@treecg/curation
Version:
This library implements a class (Curator) with methods to curate an announcement LDES in LDP.
295 lines (239 loc) • 12.4 kB
text/typescript
import {readdirSync, rmdirSync} 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 {ACLConfig, LDESConfig, LDESinSolid, Orchestrator} from "@treecg/ldes-orchestrator";
import {getSession} from "@treecg/ldes-orchestrator/dist/src/Login";
import {Store} from "n3";
import {Curator} from "../src/Curator";
import {turtleStringToStore} from "../src/util/Conversion";
import {DCT, RDF, TREE} from "../src/util/Vocabularies";
import {solidUrl, sleep, fileAsStore} from "./solidHelper";
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('Curation', () => {
const curationDirectory = 'curationTest';
const ldesName = 'ldes';
const curatedName = 'curated';
const synchronizedName = 'synchronized';
const curationbaseUrl = `${base + curationDirectory}/`;
const ldesBaseUrl = `${curationbaseUrl + ldesName}/`;
const curatedIRI = `${curationbaseUrl + curatedName}/`;
const synchronizedIRI = `${curationbaseUrl + synchronizedName}/`;
const curationConfig = {
ldesIRI: ldesBaseUrl,
curatedIRI: curatedIRI,
synchronizedIRI: synchronizedIRI
};
// path to the synchronized directory
const synchronizedPath = Path.join(solidPodPath, curationDirectory, synchronizedName);
/**
* Very specific function to get the relation store of a synced collection node
* @returns {Promise<Store<Quad, Quad, Quad, Quad>>}
*/
async function getSyncedRelationStore(): Promise<Store> {
const rootStore = await fileAsStore(Path.join(synchronizedPath, 'root.ttl'));
const relationUrl = rootStore.getQuads(null, TREE.node, null, null)[0].object.id;
const relationName = relationUrl.split('/').slice(-1)[0];
return await fileAsStore(Path.join(synchronizedPath, `${relationName}$.ttl`));
}
beforeEach(async () => {
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();
});
afterEach(async () => {
rmdirSync(Path.join(solidPodPath, curationDirectory), {recursive: true});
});
it('Synchronizing', async () => {
const response = await postAnnouncement(announcement, ldesBaseUrl) as Response;
const announcementURI = response.headers.get('location');
const curator = new Curator(curationConfig, session);
await curator.synchronize();
// read synchronize file and conform that URI of announcement
const files = readdirSync(synchronizedPath);
expect(files.includes('root.ttl')).toBe(true);
expect(files.length).toBe(2);
const relationStore = await getSyncedRelationStore();
// time object of the announcement should exist
expect(relationStore.getQuads(announcementURI, null, null, null).length).toBe(1);
});
it('Multiple synchronizing: Orchestrator was executed', async () => {
// initialise ldes, orchestrator and curator
const ldesConfig = await LDESinSolid.getConfig(ldesBaseUrl, session);
const ldes = new LDESinSolid(ldesConfig.ldesConfig, ldesConfig.aclConfig, session, 1);
const orchestrator = new Orchestrator(session);
const curator = new Curator(curationConfig, session);
// add one announcement
await postAnnouncement(announcement, ldesBaseUrl);
// synchronize once
await curator.synchronize();
// orchestrate once
orchestrator.orchestrateLDES(ldes, .1);
await sleep(1000); // make sure there was some orchestration
orchestrator.stopOrchestrating();
// add another announcement
await postAnnouncement(announcement, ldesBaseUrl);
// synchronize again
await curator.synchronize();
const files = readdirSync(synchronizedPath);
expect(files.length).toBe(3);
});
it('Multiple synchronizing: new member was added', async () => {
const curator = new Curator(curationConfig, session);
// add one announcement
const responseAnnouncement1 = await postAnnouncement(announcement, ldesBaseUrl);
// synchronize once
await curator.synchronize();
await sleep(1000);
// add one announcement
const responseAnnouncement2 = await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
const relationStore = await getSyncedRelationStore();
const announcementPresent1 = relationStore.getQuads(responseAnnouncement1.headers.get('location'), null, null, null);
const announcementPresent2 = relationStore.getQuads(responseAnnouncement2.headers.get('location'), null, null, null);
expect(announcementPresent1.length).toBe(1);
expect(announcementPresent2.length).toBe(1);
});
it('Retrieve empty list when memberUris are not synchronized', async () => {
const curator = new Curator(curationConfig, session);
await postAnnouncement(announcement, ldesBaseUrl);
const members = await curator.getRecentMembers(1);
expect(members.length).toBe(0);
});
it('Retrieve list of memberUris of synchronized list', async () => {
const curator = new Curator(curationConfig, session);
const response = await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
const members = await curator.getRecentMembers(1);
expect(members.length).toBe(1);
expect(members[0].memberIRI).toBe(response.headers.get('location'));
});
it('Extracting one member', async () => {
const curator = new Curator(curationConfig, session);
const response = await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
const members = await curator.getRecentMembers(1);
const member = await curator.extractMember(members[0].memberIRI);
expect(member.type).toBe(TREE.Node);
expect(member.value["@id"]).toBe(`${response.headers.get('location')}#view`);
});
it('Initiating the curated ldes', async () => {
const curator = new Curator(curationConfig, session);
await curator.init();
const curatedLdesPath = Path.join(solidPodPath, curationDirectory, curatedName);
const curatedFiles = readdirSync(curatedLdesPath);
expect(curatedFiles.includes('root.ttl')).toBe(true);
// files expected are: the root, the directory of the relation of the root, the acl file of the ldes and the .meta file
expect(curatedFiles.length).toBe(4);
const curatedRoot = await fileAsStore(Path.join(curatedLdesPath, 'root.ttl'));
const view = curatedRoot.getQuads(`${curatedIRI}root.ttl`, RDF.type, null, null)[0];
expect(view.object.id).toBe(TREE.Node);
});
it('Rejecting one member (giving the timestamp)', async () => {
const curator = new Curator(curationConfig, session);
await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
const members = await curator.getRecentMembers(1);
await curator.reject(members[0].memberIRI, members[0].timestamp);
const membersLeft = await curator.getRecentMembers(1);
expect(membersLeft.length).toBe(0);
});
it('Rejecting one member (not giving the timestamp)', async () => {
const curator = new Curator(curationConfig, session);
const response = await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
await curator.reject(response.headers.get('location')!);
await curator.reject(response.headers.get('location')!);
const membersLeft = await curator.getRecentMembers(1);
expect(membersLeft.length).toBe(0);
});
it('Accepting without initialising should throw error', async () => {
expect.assertions(2);
const curator = new Curator(curationConfig, session);
try {
await curator.accept('iri');
} catch (e) {
const err = e as Error;
expect(err.name).toBe("Error");
expect(err.message).toBe("First execute function init() as the curated LDES was not initialised yet");
}
});
it('Accepting one member (giving all information)', async () => {
const curator = new Curator(curationConfig, session);
await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
await curator.init();
const members = await curator.getRecentMembers(1);
const member = await curator.extractMember(members[0].memberIRI);
await curator.accept(members[0].memberIRI, member.value, members[0].timestamp);
// very ugly code to get the file where the member is added -> todo: Get it via a nice way
const curatedLdesPath = Path.join(solidPodPath, curationDirectory, curatedName);
const directoryInCuratedLdes = readdirSync(curatedLdesPath, {withFileTypes: true})
.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
const relationPath = Path.join(solidPodPath, curationDirectory, curatedName, directoryInCuratedLdes[0]);
const curatedMemberFileName = readdirSync(relationPath).filter(name => !name.includes('acl'))[0];
const curatedRoot = await fileAsStore(Path.join(relationPath, curatedMemberFileName));
const view = curatedRoot.getQuads(`${member.iri}#view`, RDF.type, null, null)[0];
expect(view.object.id).toBe(TREE.Node);
});
it('Accepting one member (giving only the memberIRI)', async () => {
const curator = new Curator(curationConfig, session);
const response = await postAnnouncement(announcement, ldesBaseUrl);
await curator.synchronize();
await curator.init();
const memberIri = response.headers.get('location');
await curator.accept(memberIri!);
// very ugly code to get the file where the member is added -> todo: Get it via a nice way
const curatedLdesPath = Path.join(solidPodPath, curationDirectory, curatedName);
const directoryInCuratedLdes = readdirSync(curatedLdesPath, {withFileTypes: true})
.filter(dirent => dirent.isDirectory()).map(dirent => dirent.name);
const relationPath = Path.join(solidPodPath, curationDirectory, curatedName, directoryInCuratedLdes[0]);
const curatedMemberFileName = readdirSync(relationPath).filter(name => !name.includes('acl'))[0];
const curatedMemberPath = Path.join(relationPath, curatedMemberFileName);
const curatedRoot = await fileAsStore(curatedMemberPath);
const view = curatedRoot.getQuads(`${memberIri}#view`, RDF.type, null, null)[0];
expect(view.object.id).toBe(TREE.Node);
});
});
});