@treecg/ldes-orchestrator
Version:
Fills the gaps that a Linked Data Platform (LDP) cannot do by itself for creating a Linked Data Event Stream (LDES) in LDP.
214 lines (172 loc) • 10 kB
text/typescript
import {readdirSync} from "fs";
import Path from "path";
import {Session} from "@rubensworks/solid-client-authn-isomorphic";
import {createViewAnnouncement, postAnnouncement} from "@treecg/ldes-announcements";
import {AnnouncementConfig} from "@treecg/ldes-announcements/dist/lib/Writer";
import {Announce} from "@treecg/ldes-announcements/dist/util/Interfaces";
import {Literal} from "n3";
import {LDESConfig, ACLConfig, LDESinSolid, Orchestrator,ACL, DCT, LDP, RDF, TREE, XSD, FOAF, LDES} from "../index";
import {getSession} from "../src/Login";
import {turtleStringToStore} from "../src/util/Conversion";
import {sleep} from "../src/util/Util";
import {fileAsStore, solidUrl} from "./solidHelper";
const parse = require('parse-link-header');
describe('Integration test for LDESinSolid and Orchestrating functionalities', () => {
const base: string = solidUrl();
let session: Session;
let announcement: Announce;
const solidPodPath = Path.join(__dirname, 'solidPod');
beforeAll(async () => {
// create session
session = await getSession();
// create announcement
const viewString = '<https://test/output/root.ttl> <http://www.w3.org/1999/02/22-rdf-syntax-ns#type> <https://w3id.org/tree#Node>.';
const viewStore = await turtleStringToStore(viewString);
const announcementConfig: AnnouncementConfig = {
bucketizer: 'substring',
creatorName: 'woutslabbinck',
creatorURL: `https://github.com/woutslabbinck`,
originalLDESURL: 'https://smartdata.dev-vlaanderen.be/base/gemeente',
pageSize: '100',
propertyPath: '<http://www.w3.org/2000/01/rdf-schema#label>',
viewId: 'https://test/output/root.ttl'
};
announcement = await createViewAnnouncement(viewStore, announcementConfig);
});
describe('General tests', () => {
it('server online', async () => {
const getRequest = await fetch(base);
expect(getRequest.status).toBe(200);
});
it('is logged in', async () => {
expect(session.info.isLoggedIn).toBe(true);
});
});
describe('LDES-Orchestrator', () => {
it('Verifying contents of created LDES in LDP', async () => {
const ldesDirectoryName = 'newLDES';
const ldesBaseUrl = `${base + ldesDirectoryName}/`;
const ldesConfig: LDESConfig = {
base: ldesBaseUrl,
treePath: DCT.modified,
shape: 'https://tree.linkeddatafragments.org/announcements/shape',
relationType: TREE.GreaterThanOrEqualToRelation
};
if (!session.info.webId) throw Error("Should be present");
const aclConfig: ACLConfig = {
agent: session.info.webId
};
const ldes = new LDESinSolid(ldesConfig, aclConfig, session, 1);
await ldes.createLDESinLDP();
const ldesPath = Path.join(solidPodPath, ldesDirectoryName);
const ldesContainerResponse = await session.fetch(ldesBaseUrl, {
method: "GET",
headers: {Accept: "text/turtle"}
});
const ldesContainerStore = await turtleStringToStore(await ldesContainerResponse.text(), ldesBaseUrl);
const inboxFromStore = ldesContainerStore.getQuads(ldesBaseUrl, LDP.inbox, null, null)[0].object.id;
const inboxFromResponse = parse(ldesContainerResponse.headers.get('Link'))[LDP.inbox];
// inbox Link header url must also be in the metadata
expect(inboxFromStore).toBe(inboxFromResponse.url);
// acl content test
const ldesRootAclResponse = await session.fetch(`${ldesBaseUrl}.acl`, {
method: "GET",
headers: {Accept: "text/turtle"}
});
const ldesRootAclStore = await turtleStringToStore(await ldesRootAclResponse.text(), `${ldesBaseUrl}.acl`);
// public contents test
const ldesRootPublicSubject = ldesRootAclStore.getQuads(null, ACL.agentClass, FOAF.Agent, null)[0].subject.id;
const ldesRootAccessRightsPublic = ldesRootAclStore.getObjects(ldesRootPublicSubject, ACL.mode, null).map(object => object.id);
expect(ldesRootAccessRightsPublic.includes(ACL.Read)).toBe(true);
// private contents test
const ldesRootPrivateSubject = ldesRootAclStore.getQuads(null, ACL.agent, session.info.webId, null)[0].subject.id;
const ldesRootAccessRightsPrivate = ldesRootAclStore.getObjects(ldesRootPrivateSubject, ACL.mode, null).map(object => object.id);
expect(ldesRootAccessRightsPrivate.includes(ACL.Read)).toBe(true);
expect(ldesRootAccessRightsPrivate.includes(ACL.Write)).toBe(true);
expect(ldesRootAccessRightsPrivate.includes(ACL.Control)).toBe(true);
// content of ldes in LDP root directory
const ldesDirectoryFiles = readdirSync(ldesPath);
const relationDirectoryName = inboxFromStore.replace(ldesBaseUrl, '').slice(0, -1);
expect(ldesDirectoryFiles.includes('.meta')).toBe(true);
expect(ldesDirectoryFiles.includes('.acl$.jsonld') || ldesDirectoryFiles.includes('.acl')).toBe(true);
expect(ldesDirectoryFiles.includes('root.ttl')).toBe(true);
expect(ldesDirectoryFiles.includes(relationDirectoryName)).toBe(true);
expect(ldesDirectoryFiles.length).toBe(4);
// content of root
const rootStore = await fileAsStore(Path.join(ldesPath, 'root.ttl'));
const rootIRI = `${ldesBaseUrl}root.ttl`;
const collectionIRI = rootStore.getSubjects(RDF.type, LDES.EventStream, null)[0].id;
expect(rootStore.getObjects(collectionIRI, TREE.shape, null)[0].id).toBe('https://tree.linkeddatafragments.org/announcements/shape');
expect(rootStore.getObjects(collectionIRI, TREE.view, null)[0].id).toBe(rootIRI);
expect(rootStore.getObjects(rootIRI, RDF.type, null)[0].id).toBe(TREE.Node);
expect(rootStore.getObjects(rootIRI, TREE.relation, null).length).toBe(1);
const relationNode = rootStore.getObjects(rootIRI, TREE.relation, null)[0].id;
expect(rootStore.getObjects(relationNode, RDF.type, null)[0].id).toBe(ldesConfig.relationType);
expect(rootStore.getObjects(relationNode, TREE.path, null)[0].id).toBe(ldesConfig.treePath);
expect(rootStore.getObjects(relationNode, TREE.node, null)[0].id).toBe(`${ldesBaseUrl + relationDirectoryName}/`);
expect((rootStore.getObjects(relationNode, TREE.value, null)[0] as Literal).datatype.value).toBe(XSD.dateTime);
// content of first relation directory
const relationDirectoryPath = Path.join(ldesPath, relationDirectoryName);
const relationDirectoryFiles = readdirSync(relationDirectoryPath);
expect(relationDirectoryFiles.includes('.acl$.jsonld') || ldesDirectoryFiles.includes('.acl')).toBe(true);
// expect(relationDirectoryFiles.includes('.meta')).toBe(true) // Note: only present in shape solid server
expect(relationDirectoryFiles.length).toBe(1);
// acl content test of relation container
const relationAclResponse = await session.fetch(`${ldesBaseUrl + relationDirectoryName}/.acl`, {
method: "GET",
headers: {Accept: "text/turtle"}
});
const relationAclStore = await turtleStringToStore(await relationAclResponse.text(), `${ldesBaseUrl}.acl`);
const relationPublicSubject = relationAclStore.getQuads(null, ACL.agentClass, FOAF.Agent, null)[0].subject.id;
const relationAccessRightsPublic = relationAclStore.getObjects(relationPublicSubject, ACL.mode, null).map(object => object.id);
expect(relationAccessRightsPublic.includes(ACL.Read)).toBe(true);
expect(relationAccessRightsPublic.includes(ACL.Append)).toBe(true);
const relationPrivateSubject = relationAclStore.getQuads(null, ACL.agent, session.info.webId, null)[0].subject.id;
const relationAccessRightsPrivate = relationAclStore.getObjects(relationPrivateSubject, ACL.mode, null).map(object => object.id);
expect(relationAccessRightsPrivate.includes(ACL.Read)).toBe(true);
expect(relationAccessRightsPrivate.includes(ACL.Write)).toBe(true);
expect(relationAccessRightsPrivate.includes(ACL.Control)).toBe(true);
// shape url should be in metadata Note: Only present in shape solid server
// const relationMetaStore = await fileAsStore(Path.join(relationDirectoryPath, '.meta'))
// expect(relationMetaStore.getObjects(null, LDP.constrainedBy, null)[0].id)
// .toBe('https://tree.linkeddatafragments.org/announcements/shape')
});
it('verify ldes in solid works', async () => {
/**
* This function creates an ldes, adds an announcement and verifies that orchestration works.
* That is that after the limit of 1 was reached, a new container is created
*/
const ldesBaseUrl = `${base}ldes/`;
const ldesConfig: LDESConfig = {
base: ldesBaseUrl,
treePath: DCT.modified,
shape: 'https://tree.linkeddatafragments.org/announcements/shape',
relationType: TREE.GreaterThanOrEqualToRelation
};
if (!session.info.webId) throw Error("Should be present");
const aclConfig: ACLConfig = {
agent: session.info.webId
};
const ldes = new LDESinSolid(ldesConfig, aclConfig, session, 1);
await ldes.createLDESinLDP();
const firstContainer = await ldes.getCurrentContainer();
const emptyLDES = await ldes.getAmountResources();
expect(emptyLDES).toBe(0);
const response = await postAnnouncement(announcement, ldesBaseUrl);
expect(response.status).toBe(201);
const oneResource = await ldes.getAmountResources();
expect(oneResource).toBe(1);
const orchestrator = new Orchestrator(session);
orchestrator.orchestrateLDES(ldes, .1);
await sleep(200);
orchestrator.stopOrchestrating();
await sleep(1000);
const againEmpty = await ldes.getAmountResources();
expect(againEmpty).toBe(0);
const secondContainer = await ldes.getCurrentContainer();
expect(firstContainer).not.toBe(secondContainer);
expect(firstContainer).toContain(ldesBaseUrl);
expect(secondContainer).toContain(ldesBaseUrl);
});
});
});