UNPKG

@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
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); }); }); });