diff --git a/demo/dynamic-policy.ts b/demo/dynamic-policy.ts new file mode 100644 index 0000000..6ac6988 --- /dev/null +++ b/demo/dynamic-policy.ts @@ -0,0 +1,107 @@ +import { Parser, Writer } from 'n3'; +import { materializePolicy, ODRLEngineMultipleSteps, ODRLEvaluator, resourceToOptimisedTurtle } from "../dist/index"; + + +// Variant on test case 036: Read request from Alice to resource X returns into yes (temporal lt) (Alice Request Read X). +// https://github.com/SolidLabResearch/ODRL-Test-Suite/blob/main/data/test_cases/testcase-036-alice-read-x.ttl +const policy = ` +@prefix odrl: . +@prefix ex: . +@prefix dct: . +@prefix odrluc: . + + a odrl:Set ; + odrl:uid ; + dct:description "ALICE may READ resource X when it is before 'ex:updateValue' (see sotw)." ; + dct:source ; + odrl:permission . + + a odrl:Permission ; + odrl:assignee ex:alice ; + odrl:action odrl:read ; + odrl:target ex:x ; + odrl:constraint . + + odrl:leftOperand odrl:dateTime ; + odrl:operator odrl:lt ; + odrl:rightOperandReference ex:operandReference1 . + +ex:operandReference1 a odrluc:OperandReference ; + odrluc:reference ex:externalSource ; + odrluc:path ex:updatedValue . +` + +// state of the world -> with external value indicating the time +const sotw = ` +@prefix ex: . +@prefix temp: . +@prefix dct: . + + a ex:Sotw ; + ex:includes temp:currentTime . + +temp:currentTime dct:issued "2017-02-12T11:20:10.999Z"^^ . + +# external value that will be materialized in the policy +ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^ . +` + +const request = ` +@prefix odrl: . +@prefix ex: . +@prefix dct: . + + a odrl:Request ; + odrl:uid ; + dct:description "Requesting Party ALICE requests to READ resource X." ; + odrl:permission . + + a odrl:Permission ; + odrl:assignee ex:alice ; + odrl:action odrl:read ; + odrl:target ex:x . +` + +async function main() { + const parser = new Parser() + const writer = new Writer() + + const odrlDynamicPolicyQuads = parser.parse(policy) + + const odrlRequestQuads = parser.parse(request) + const stateOfTheWorldQuads = parser.parse(sotw) + + const instantiatedPolicyQuads = materializePolicy(odrlDynamicPolicyQuads, stateOfTheWorldQuads) + + console.log("Instantiated Policy:") + console.log(writer.quadsToString(instantiatedPolicyQuads)); + + + // reasoning over dynamic policy + const evaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps()); + const reasoningResult = await evaluator.evaluate( + odrlDynamicPolicyQuads, + odrlRequestQuads, + stateOfTheWorldQuads) + + const output = writer.quadsToString(reasoningResult); + console.log("Compliance Report") + console.log(output); + + const prefixes = { + 'odrl': 'http://www.w3.org/ns/odrl/2/', + 'ex': 'http://example.org/', + 'temp': 'http://example.com/request/', + 'dct': 'http://purl.org/dc/terms/', + 'xsd': 'http://www.w3.org/2001/XMLSchema#', + 'foaf': 'http://xmlns.com/foaf/0.1/', + 'report': 'https://w3id.org/force/compliance-report#' + } + + // created report with N3 + // @ts-ignore + console.log(resourceToOptimisedTurtle(reasoningResult, prefixes)); + +} +main() + diff --git a/package-lock.json b/package-lock.json index 33a8bde..ab81132 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,10 @@ "eyereasoner": "^16.18.4", "n3": "^1.20.4", "rdf-isomorphic": "^1.3.1", + "rdf-lens": "^1.3.5", "rdf-parse": "^3.0.0", "rdf-store-stream": "^2.0.1", - "rdf-vocabulary": "^1.0.0", + "rdf-vocabulary": "^1.0.1", "streamify-string": "^1.0.1", "tmp": "^0.2.3", "uuidv4": "^6.2.13" @@ -1210,6 +1211,17 @@ "url": "https://github.com/sponsors/Borewit" } }, + "node_modules/@treecg/types": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/@treecg/types/-/types-0.4.6.tgz", + "integrity": "sha512-iL8rBhAX4INvfuHZzJ1RELh1i/fspviylENZTk6D2p/xDazY1pTkd+y/hxKYVWCC0jHNolDWRsH7ozwaFGNQKA==", + "dependencies": { + "@rdfjs/types": "*", + "loglevel": "^1.8.1", + "loglevel-plugin-prefix": "^0.8.4", + "rdf-data-factory": "^1.1.0" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", @@ -3504,6 +3516,23 @@ "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", "dev": true }, + "node_modules/loglevel": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.9.2.tgz", + "integrity": "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==", + "engines": { + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" + } + }, + "node_modules/loglevel-plugin-prefix": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/loglevel-plugin-prefix/-/loglevel-plugin-prefix-0.8.4.tgz", + "integrity": "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g==" + }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -3986,6 +4015,38 @@ "rdf-terms": "^1.7.0" } }, + "node_modules/rdf-lens": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/rdf-lens/-/rdf-lens-1.3.5.tgz", + "integrity": "sha512-tSHPMRNnkJEYUXgm9Fb6Xbhc54wXfSsc9eIH3PkK6NLTGRUIMDVCcLxXVkj+IyvkemCjWPsweAx2/FX9ZwLZUQ==", + "dependencies": { + "@rdfjs/types": "^2.0.1", + "@treecg/types": "^0.4.6", + "@types/n3": "^1.21.1", + "n3": "^1.23.1", + "rdf-data-factory": "^2.0.2" + } + }, + "node_modules/rdf-lens/node_modules/@rdfjs/types": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rdfjs/types/-/types-2.0.1.tgz", + "integrity": "sha512-uyAzpugX7KekAXAHq26m3JlUIZJOC0uSBhpnefGV5i15bevDyyejoB7I+9MKeUrzXD8OOUI3+4FeV1wwQr5ihA==", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rdf-lens/node_modules/rdf-data-factory": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rdf-data-factory/-/rdf-data-factory-2.0.2.tgz", + "integrity": "sha512-WzPoYHwQYWvIP9k+7IBLY1b4nIDitzAK4mA37WumAF/Cjvu/KOtYJH9IPZnUTWNSd5K2+pq4vrcE9WZC4sRHhg==", + "dependencies": { + "@rdfjs/types": "^2.0.0" + }, + "funding": { + "type": "individual", + "url": "https://github.com/sponsors/rubensworks/" + } + }, "node_modules/rdf-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/rdf-parse/-/rdf-parse-3.0.0.tgz", @@ -4058,11 +4119,11 @@ } }, "node_modules/rdf-vocabulary": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rdf-vocabulary/-/rdf-vocabulary-1.0.0.tgz", - "integrity": "sha512-aDMeOiyr7ZUx9UjjgQogXW5e+KfJSxa+HML5BIXE2lQiXp+qn9FggfnejBkcnqUCoypFtjDvejZ3UN3gGkurTw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rdf-vocabulary/-/rdf-vocabulary-1.0.1.tgz", + "integrity": "sha512-0I7osG03qJ/7O/cEY/1wDLWnx+JBPoxPa9Iz/8856W7Ci7XNLBj8ZSWZCqVCOi/v5P6nenw5V9gkmPkg5TGZkg==", "dependencies": { - "@rdfjs/types": "^1.1.0" + "@rdfjs/types": "*" } }, "node_modules/rdfa-streaming-parser": { diff --git a/package.json b/package.json index c4c15b7..2adf9e9 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "clean": "rm -rf ./dist", "demo:test-engine": "ts-node demo/test-n3-engine.ts", "demo:test-evaluator": "ts-node demo/test-n3-evaluator.ts", + "demo:dynamic-policy": "ts-node demo/dynamic-policy.ts", "prepare": "npm run build", "release": "npm run build && npm publish --access public", "test": "jest" @@ -39,8 +40,8 @@ "@types/jest": "^29.5.12", "@types/tmp": "^0.2.6", "jest": "^29.7.0", - "ts-jest": "^29.2.5", "jest-rdf": "^2.0.0", + "ts-jest": "^29.2.5", "ts-node": "^10.9.2", "typescript": "^5.4.5" }, @@ -50,9 +51,10 @@ "eyereasoner": "^16.18.4", "n3": "^1.20.4", "rdf-isomorphic": "^1.3.1", + "rdf-lens": "^1.3.5", "rdf-parse": "^3.0.0", "rdf-store-stream": "^2.0.1", - "rdf-vocabulary": "^1.0.0", + "rdf-vocabulary": "^1.0.1", "streamify-string": "^1.0.1", "tmp": "^0.2.3", "uuidv4": "^6.2.13" diff --git a/src/evaluator/DynamicConstraint.ts b/src/evaluator/DynamicConstraint.ts new file mode 100644 index 0000000..b1edd26 --- /dev/null +++ b/src/evaluator/DynamicConstraint.ts @@ -0,0 +1,66 @@ +import { Quad, Term } from "@rdfjs/types"; +import { Literal, NamedNode, Store } from 'n3'; +import { BasicLensM, CBDLens, Cont, pred, ShaclPath } from "rdf-lens"; +import { ODRL, ODRLUC, RDF } from "../util/Vocabularies"; + +// use RDF lens instead of clownface for SHACL Property Paths: https://ceur-ws.org/Vol-3759/paper13.pdf +export const pathLens = pred(ODRLUC.terms.path) + .one() + .then(ShaclPath); + +// Utility function to extract the SHACL property path of the Dynamic ODRL Policy using `odrluc:path` +export function getPath(id: Term, quads: Quad[]): BasicLensM { + return pathLens.execute({ id, quads }).mapAll((x) => x.id); +} + +// Utility function to extract the value from an external resource using a SHACL Property path +export function usePath(id: Term, quads: Quad[], lens: BasicLensM): Term[] { + return lens.execute({ id, quads }) +} + +// implements dynamic policy constraint algorithm +// algorithm to fetch value using SHACL path from sotw in policy +// will only materialize when RightOperandReference class is present +export function materializePolicy(dynamicPolicy: Quad[], stateOfTheWorld: Quad[]): Quad[] { + const odrlPolicyStore = new Store(dynamicPolicy) + const odrlDynamicPolicyStore = new Store(dynamicPolicy) + + // get all constraints containing references + const operandReferenceNodes = odrlDynamicPolicyStore.getSubjects(RDF.terms.type, ODRLUC.terms.OperandReference, null); + + for (const operandReferenceNode of operandReferenceNodes) { + // ODRL constraint that has that given rightOperandReference + const constraintNodes = odrlDynamicPolicyStore.getSubjects(ODRL.terms.rightOperandReference, operandReferenceNode, null) + + // external resource + const externalResource = odrlDynamicPolicyStore.getObjects(operandReferenceNode, ODRLUC.terms.reference, null)[0] + if (!externalResource) { + throw Error("There is no source present in the Dynamic Policy Constraint.") + } + + // SHACL Property path + const lens = getPath(operandReferenceNode, dynamicPolicy); + + // extract SHACL Property Path using RDF-Lens + const getValue = usePath(externalResource, stateOfTheWorld, lens); + const instantiatedValue = getValue[0]; // according to the algorithm (see paper), there should be only one term + + if (!(instantiatedValue instanceof NamedNode || instantiatedValue instanceof Literal)) { + // Now instantiatedValue is a valid value + throw Error("Instantiated value is not a proper value."); + } + for (const constraintNode of constraintNodes) { + // remove rightOperandRefrence triple from constraint + odrlPolicyStore.removeQuad(constraintNode, ODRL.terms.rightOperandReference, operandReferenceNode) + // add materialized rightOperand + odrlPolicyStore.addQuad(constraintNode, ODRL.terms.rightOperand, instantiatedValue) + } + + // remove OperandReference triples from Policy store + const operandReferenceQuads = CBDLens.execute({id:operandReferenceNode, quads: dynamicPolicy}) + odrlPolicyStore.removeQuads(operandReferenceQuads) + } + return odrlPolicyStore.getQuads(null, null, null, null) + } + + \ No newline at end of file diff --git a/src/evaluator/Evaluate.ts b/src/evaluator/Evaluate.ts index 8ebf411..78a5646 100644 --- a/src/evaluator/Evaluate.ts +++ b/src/evaluator/Evaluate.ts @@ -1,6 +1,7 @@ import type { Quad } from '@rdfjs/types'; import { ODRLEngine, Engine} from './Engine'; import { RDFValidator, TripleTermValidator, SHACLValidator } from './Validate'; +import { materializePolicy } from './DynamicConstraint'; export interface Evaluator { /** @@ -64,9 +65,11 @@ export class ODRLEvaluator implements Evaluator { // if there are compact policies -> they must be expanded (also reocmmended by ODRL ยง 2.7.1 Compact Policy) + // handle dynamic policies + const instantiatedPolicies = materializePolicy(policy, state); // evaluate // the evaluation will result into a conformance report - const evaluation = await this.engine.evaluate([...policy, ...request, ...state]) + const evaluation = await this.engine.evaluate([...instantiatedPolicies, ...request, ...state]) // TODO: think about when the report can be empty // does it always mean there is not enough information? diff --git a/src/index.browser.ts b/src/index.browser.ts index e4cdd8f..0901d8c 100644 --- a/src/index.browser.ts +++ b/src/index.browser.ts @@ -1,11 +1 @@ -export * from './evaluator/Engine' -export * from './evaluator/Evaluate' -export * from './evaluator/Validate' - -export * from './reasoner/EyeJsReasoner' -// export * from './reasoner/EyeReasoner' -export * from './reasoner/Reasoner' - -export * from './util/Notation3Util' -// export * from './util/RDFUtil' -export * from './rules/Rules' \ No newline at end of file +export * from "./index.core" \ No newline at end of file diff --git a/src/index.core.ts b/src/index.core.ts new file mode 100644 index 0000000..cf8c0f5 --- /dev/null +++ b/src/index.core.ts @@ -0,0 +1,12 @@ +export * from './evaluator/Engine' +export * from './evaluator/Evaluate' +export * from './evaluator/Validate' +export * from './evaluator/DynamicConstraint' + +export * from './reasoner/EyeJsReasoner' +export * from './reasoner/Reasoner' + +export * from './util/Notation3Util' +export * from './util/Vocabularies' + +export * from './rules/Rules' \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 06a7431..78a8326 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,10 +1,4 @@ -export * from './evaluator/Engine' -export * from './evaluator/Evaluate' -export * from './evaluator/Validate' - -export * from './reasoner/EyeJsReasoner' +export * from "./index.core" export * from './reasoner/EyeReasoner' -export * from './reasoner/Reasoner' -export * from './util/Notation3Util' export * from './util/RDFUtil' \ No newline at end of file diff --git a/src/reasoner/EyeJsReasoner.ts b/src/reasoner/EyeJsReasoner.ts index 5b5694b..cdfe28e 100644 --- a/src/reasoner/EyeJsReasoner.ts +++ b/src/reasoner/EyeJsReasoner.ts @@ -1,4 +1,4 @@ -import { n3reasoner } from "eyereasoner/dist"; +import { n3reasoner } from "eyereasoner"; import { Reasoner } from "./Reasoner"; export class EyeJsReasoner extends Reasoner { diff --git a/src/util/Vocabularies.ts b/src/util/Vocabularies.ts new file mode 100644 index 0000000..9b7cce8 --- /dev/null +++ b/src/util/Vocabularies.ts @@ -0,0 +1,42 @@ +import { createVocabulary } from 'rdf-vocabulary'; + +export const RDF = createVocabulary( + 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'type', +); + +export const ODRL = createVocabulary( + 'http://www.w3.org/ns/odrl/2/', + 'Agreement', + 'Offer', + 'Permission', + 'Policy', + 'Request', + 'Set', + 'action', + 'target', + 'assignee', + 'assigner', + 'constraint', + 'operator', + 'permission', + 'dateTime', + 'purpose', + 'uid', + 'leftOperand', + 'rightOperand', + 'rightOperandReference', + 'gt', + 'gteq', + 'lt', + 'lteq', + 'eq', + 'neq', + 'read' +); + +export const ODRLUC = createVocabulary("https://w3id.org/force/odrlproposed#", + "OperandReference", + "reference", + "path" +) \ No newline at end of file diff --git a/test/integration/ODRL-Evaluator.test.ts b/test/integration/ODRL-Evaluator.test.ts index 6d52aa5..2c8f415 100644 --- a/test/integration/ODRL-Evaluator.test.ts +++ b/test/integration/ODRL-Evaluator.test.ts @@ -1,10 +1,12 @@ -import { ODRLEngineMultipleSteps, ODRLEvaluator, turtleStringToStore, blanknodeify } from "../../src"; -import { Quad } from "n3"; import "jest-rdf"; +import { Parser, Quad } from "n3"; +import { blanknodeify, ODRLEngineMultipleSteps, ODRLEvaluator } from "../../src"; describe('The default ODRL evaluator', () => { const odrlEvaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps()); - it ('Handle skos exact match', async () => { + const parser = new Parser() + + it('handles skos exact match.', async () => { const odrlPolicyText = ` . . @@ -14,7 +16,7 @@ describe('The default ODRL evaluator', () => { . . `; -const odrlRequestText = ` + const odrlRequestText = ` . . . @@ -22,10 +24,10 @@ const odrlRequestText = ` . . `; -const stateOfTheWorldText = ` + const stateOfTheWorldText = ` "2025-01-14T10:09:08.370Z"^^ . `; - const expectedReport =` + const expectedReport = ` @prefix odrl: . @prefix ex: . @prefix temp: . @@ -52,15 +54,118 @@ const stateOfTheWorldText = ` a report:ActionReport; report:satisfactionState report:Satisfied.` - const odrlPolicyStore = await turtleStringToStore(odrlPolicyText); - const odrlRequestStore = await turtleStringToStore(odrlRequestText); - const stateOfTheWorldStore = await turtleStringToStore(stateOfTheWorldText); - const expectedReportStore = await turtleStringToStore(expectedReport); - const report = await odrlEvaluator.evaluate( - odrlPolicyStore.getQuads(null, null, null, null), - odrlRequestStore.getQuads(null, null, null, null), - stateOfTheWorldStore.getQuads(null, null, null, null)); - - expect(blanknodeify(report as any as Quad[])).toBeRdfIsomorphic(blanknodeify(expectedReportStore.getQuads(null, null, null, null))) + const odrlPolicyQuads = parser.parse(odrlPolicyText); + const odrlRequestQuads = parser.parse(odrlRequestText); + const stateOfTheWorldQuads = parser.parse(stateOfTheWorldText); + + const expectedReportQuads = parser.parse(expectedReport); + const report = await odrlEvaluator.evaluate( + odrlPolicyQuads, + odrlRequestQuads, + stateOfTheWorldQuads); + + expect(blanknodeify(report as any as Quad[])).toBeRdfIsomorphic(blanknodeify(expectedReportQuads)) }); + + it('handles dynamic constraint policies.', async () => { + const policy = ` + @prefix odrl: . + @prefix ex: . + @prefix dct: . + @prefix odrluc: . + + a odrl:Set ; + odrl:uid ; + dct:description "ALICE may READ resource X when it is before 'ex:updateValue' (see sotw)." ; + dct:source ; + odrl:permission . + + a odrl:Permission ; + odrl:assignee ex:alice ; + odrl:action odrl:read ; + odrl:target ex:x ; + odrl:constraint . + + odrl:leftOperand odrl:dateTime ; + odrl:operator odrl:lt ; + odrl:rightOperandReference ex:operandReference1 . + + ex:operandReference1 a odrluc:OperandReference ; + odrluc:reference ex:externalSource ; + odrluc:path ex:updatedValue . + ` + + const sotw = ` + @prefix ex: . + @prefix temp: . + @prefix dct: . + + a ex:Sotw ; + ex:includes temp:currentTime . + + temp:currentTime dct:issued "2017-02-12T11:20:10.999Z"^^ . + + # external value that will be materialized in the policy + ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^ . + ` + + const request = ` + @prefix odrl: . + @prefix ex: . + @prefix dct: . + + a odrl:Request ; + odrl:uid ; + dct:description "Requesting Party ALICE requests to READ resource X." ; + odrl:permission . + + a odrl:Permission ; + odrl:assignee ex:alice ; + odrl:action odrl:read ; + odrl:target ex:x . + ` + + const expectedReport = ` +@prefix odrl: . +@prefix dct: . +@prefix xsd: . +@prefix report: . + + + a report:PolicyReport; + dct:created "2017-02-12T11:20:10.999Z"^^xsd:dateTime; + report:policy ; + report:policyRequest ; + report:ruleReport . + a report:PermissionReport; + report:attemptState report:Attempted; + report:rule ; + report:ruleRequest ; + report:premiseReport , , , ; + report:activationState report:Active. + a report:TargetReport; + report:satisfactionState report:Satisfied. + a report:PartyReport; + report:satisfactionState report:Satisfied. + a report:ActionReport; + report:satisfactionState report:Satisfied. + a report:ConstraintReport; + report:constraint ; + report:constraintLeftOperand "2017-02-12T11:20:10.999Z"^^xsd:dateTime; + report:constraintOperator odrl:lt; + report:constraintRightOperand "2018-02-12T11:20:10.999Z"^^xsd:dateTime; + report:satisfactionState report:Satisfied. +` + const odrlPolicyQuads = parser.parse(policy); + const odrlRequestQuads = parser.parse(request); + const stateOfTheWorldQuads = parser.parse(sotw); + + const expectedReportQuads = parser.parse(expectedReport); + const report = await odrlEvaluator.evaluate( + odrlPolicyQuads, + odrlRequestQuads, + stateOfTheWorldQuads); + expect(blanknodeify(report as any as Quad[])).toBeRdfIsomorphic(blanknodeify(expectedReportQuads)) + + }) }) \ No newline at end of file diff --git a/test/unit/evaluator/DynamicConstraint.test.ts b/test/unit/evaluator/DynamicConstraint.test.ts new file mode 100644 index 0000000..6deeb82 --- /dev/null +++ b/test/unit/evaluator/DynamicConstraint.test.ts @@ -0,0 +1,91 @@ +import { Parser } from "n3"; +import "jest-rdf"; +import { materializePolicy } from "../../../src"; + +describe('The Dynamic Constraints materialize function', () => { + const dynamicPolicy = ` +@prefix odrl: . +@prefix ex: . +@prefix dct: . +@prefix odrluc: . + + a odrl:Set ; + odrl:uid ; + odrl:permission . + + a odrl:Permission ; + odrl:assignee ex:alice ; + odrl:action odrl:read ; + odrl:target ex:x ; + odrl:constraint . + + odrl:leftOperand odrl:dateTime ; + odrl:operator odrl:lt ; + odrl:rightOperandReference ex:operandReference1 . + +ex:operandReference1 a odrluc:OperandReference ; + odrluc:reference ex:externalSource ; + odrluc:path ex:updatedValue . +` + const sotw = ` +@prefix ex: . +@prefix dct: . + +ex:externalSource ex:updatedValue "2018-02-12T11:20:10.999Z"^^ . + ` + const instantiatedPolicy = ` + . + . + . + . + . + . + . + . + . + . + "2018-02-12T11:20:10.999Z"^^ . + ` + const parser = new Parser(); + const odrlDynamicPolicyQuads = parser.parse(dynamicPolicy); + const stateOfTheWorldQuads = parser.parse(sotw); + const instantiatedPolicyQuads = parser.parse(instantiatedPolicy); + + it("materializes when a dynamic policy and relevant sotw is provided.", () => { + const materializedPolicyQuads = materializePolicy(odrlDynamicPolicyQuads, stateOfTheWorldQuads) + + expect(materializedPolicyQuads).toBeRdfIsomorphic(instantiatedPolicyQuads); + }); + + it("does nothing when an instantiated policy is presented.", () => { + const materializedPolicyQuads = materializePolicy(instantiatedPolicyQuads, stateOfTheWorldQuads) + + expect(materializedPolicyQuads).toBeRdfIsomorphic(instantiatedPolicyQuads); + }) + + it("throws an error when no external source is provided in the sotw.", () => { + expect(() => materializePolicy(odrlDynamicPolicyQuads, [])).toThrow(Error) + }) + + it("throws an error when no external source property is provided.", () => { + const operandReference = ` +@prefix ex: . +@prefix odrluc: . +ex:operandReference1 a odrluc:OperandReference ; + odrluc:path ex:updatedValue .` + + const operandReferenceQuads = parser.parse(operandReference) + expect(() => materializePolicy(operandReferenceQuads, [])).toThrow(Error) + }) + + it("throws nope when no path property is provided.", () => { + const operandReference = ` +@prefix ex: . +@prefix odrluc: . +ex:operandReference1 a odrluc:OperandReference ; + odrluc:reference ex:externalSource .` + + const operandReferenceQuads = parser.parse(operandReference) + expect(() => materializePolicy(operandReferenceQuads, [])).toThrow("nope") + }) +}) \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 341e5f3..c2a8f69 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "outDir": "dist", "preserveConstEnums": true, "sourceMap": true, - "stripInternal": true + "stripInternal": true, + "module": "NodeNext", // required for ESM and commonjs library combination: https://stackoverflow.com/questions/71463698/why-we-need-nodenext-typescript-compiler-option-when-we-have-esnext }, "include": [ "src"