Skip to content

Commit d62dafc

Browse files
committed
feat: utilities + index created such that browsers can easily load test cases
1 parent 55cb91a commit d62dafc

9 files changed

Lines changed: 241 additions & 19 deletions

File tree

data/index.ttl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9> <http://purl.org/dc/terms/title> "Any request results into yes (Alice Request)." .
2+
<urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9> <http://example.org/policySource> <https://raw.githubusercontent.com/SolidLabResearch/ODRL-Test-Suite/refs/heads/main/data/policies/policy-1.ttl> .
3+
<urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9> <http://example.org/requestSource> <https://raw.githubusercontent.com/SolidLabResearch/ODRL-Test-Suite/refs/heads/main/data/requests/request-1.ttl> .
4+
<urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9> <http://example.org/sotwSource> <https://raw.githubusercontent.com/SolidLabResearch/ODRL-Test-Suite/refs/heads/main/data/sotw/temporal.ttl> .
5+
<urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9> <http://example.org/expectedReportSource> <https://raw.githubusercontent.com/SolidLabResearch/ODRL-Test-Suite/refs/heads/main/data/test_cases/testcase-001-alice.ttl> .

demo/test-suite.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ async function main() {
2828

2929
// evaluate function -> loop over test cases and evaluate
3030
// const engine = new ODRLEngineMultipleSteps(); // EYE JS engine
31-
const engine = new ODRLEngineMultipleSteps(new EyeReasoner('/usr/local/bin/eye', ["--quiet", "--nope", "--pass-only-new"])); // EYE local
31+
const engine = new ODRLEngineMultipleSteps({reasoner: new EyeReasoner('/usr/local/bin/eye', ["--quiet", "--nope", "--pass-only-new"])}); // EYE local
3232
const odrlEvaluator = new ODRLEvaluator(engine);
3333
const comparison = ComplianceReportComparator.simple;
3434
const testCaseEvaluator = new TestCaseEvaluator(odrlEvaluator, comparison);

install.md

Lines changed: 0 additions & 8 deletions
This file was deleted.

package-lock.json

Lines changed: 15 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
{
22
"name": "odrl-test-suite",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "An open implementation of an ODRL Evaluator that evaluates ODRL policies by generating Compliance Reports",
55
"main": "./dist/index.js",
66
"types": "./dist/index.d.ts",
7+
"browser": {
8+
"./dist/index.js": "./dist/index.browser.js",
9+
"./dist/index.d.ts": "./dist/index.browser.d.ts"
10+
},
711
"scripts": {
8-
"build": "npm run clean && tsc && npm run copy-files",
12+
"build": "npm run clean && tsc && npm run createIndex",
13+
"createIndex": "npx ts-node scripts/createIndex.ts",
914
"clean": "rm -rf ./dist",
10-
"copy-files": "cp -r ./src/rules/ ./dist/",
1115
"demo:test-suite": "ts-node demo/test-suite.ts",
16+
"prepare": "npm run build",
17+
"release": "npm run build && npm publish --access public",
1218
"test": "jest"
1319
},
1420
"repository": {
@@ -36,7 +42,7 @@
3642
"@rdfjs/types": "^1.1.0",
3743
"@types/n3": "1.16.3",
3844
"n3": "1.20.4",
39-
"odrl-evaluator": "^0.1.0",
45+
"odrl-evaluator": "^0.2.0",
4046
"rdf-isomorphic": "^1.3.1",
4147
"rdf-parse": "^3.0.0",
4248
"rdf-store-stream": "^2.0.1",

scripts/createIndex.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { writeFileSync } from "fs";
2+
import { Store, Writer } from "n3";
3+
import { ODRLEngineMultipleSteps, ODRLEvaluator } from "odrl-evaluator";
4+
import * as path from "path";
5+
import {
6+
createIndex,
7+
loadTestCases,
8+
loadWebTestCase,
9+
TestCase
10+
} from "../src";
11+
12+
13+
// Note: this can give problems -> need proper cli functionality with default current rootDir
14+
const rootDir = path.join(__dirname,"..", "data");
15+
const policiesDir = path.join(rootDir, "policies");
16+
const requestsDir = path.join(rootDir, "requests");
17+
const testCasesDir = path.join(rootDir, "test_cases");
18+
const stateOfTheWorldDir = path.join(rootDir, "sotw");
19+
20+
const online = "https://raw.githubusercontent.com/SolidLabResearch/ODRL-Test-Suite/refs/heads/main/"
21+
22+
async function main() {
23+
console.log(`Loading all test cases: policies, requests and test case (state of the world, expected compliance report and test case)`);
24+
25+
const testCaseMap = await loadTestCases(policiesDir, requestsDir, testCasesDir, stateOfTheWorldDir);
26+
const testCases: TestCase[] = [];
27+
testCaseMap.forEach((testCase) => testCases.push(testCase));
28+
console.log(`Test cases loaded.`);
29+
30+
31+
console.log("Create Index over all test cases");
32+
33+
// creating the index -> store and publish
34+
const index = createIndex([testCases[0]], {baseIRI:online, projectDirectory: path.join(__dirname,"..")});
35+
36+
const indexPath = path.join(rootDir, 'index.ttl')
37+
console.log(`Store index at '${indexPath}'`);
38+
writeFileSync(indexPath, new Writer().quadsToString(index));
39+
console.log(`When pushing code, this will be at ${online}data/index.ttl`);
40+
console.log();
41+
42+
// testing the index that we just created
43+
console.log("Test Fetching using index:");
44+
const testCaseID = 'urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9' // test case 1
45+
// const testCaseID = 'urn:uuid:296a3e9d-af68-4d1c-8acd-f2958c3d7a8a' // test case 16
46+
console.log(`Test case ID: ${testCaseID}`);
47+
console.log(new Writer().quadsToString(new Store(index).getQuads(testCaseID, null, null, null)));
48+
console.log();
49+
50+
console.log("Fetch test case from web");
51+
const onlineTestCase = await loadWebTestCase(testCaseID, index);
52+
const { policy, request, stateOfTheWorld, title } = onlineTestCase;
53+
console.log("Successful");
54+
55+
console.log("Evaluate");
56+
57+
const odrlEvaluator = new ODRLEvaluator(new ODRLEngineMultipleSteps());
58+
const evaluation = await odrlEvaluator.evaluate(policy.quads, request.quads, stateOfTheWorld.quads);
59+
60+
console.log(new Writer().quadsToString(evaluation));
61+
}
62+
main()
63+

src/index.browser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./test_cases/IndexUtil"

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export * from "./input/ParseInput"
44
export * from "./output/StoreOutput"
55

66
export * from "./test_cases/ComplianceReportUtil"
7+
export * from "./test_cases/IndexUtil"
78
export * from "./test_cases/ODRLUtil"
89
export * from "./test_cases/TestCaseUtil"
910
export * from "./test_cases/SotwUtil"

src/test_cases/IndexUtil.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { Quad, DataFactory, Parser, Store } from "n3";
2+
import { TestCase } from "../Interfaces";
3+
import { DCT } from "../Vocabularies";
4+
5+
const { quad, namedNode, literal } = DataFactory
6+
7+
/**
8+
* Converts a list of test cases into RDF Quads and creates an index.
9+
*
10+
* This method processes each `TestCase` object by:
11+
* - Transforming the source fields (e.g., `policy`, `request`, etc.) from local paths to URIs using `transformSourceTestCase`.
12+
* - Generating RDF Quads for the transformed test case via `createQuadsTestCase`.
13+
*
14+
* @param testCases - An array of `TestCase` objects to process.
15+
* @param baseIRI - The base IRI that replaces the directory paths in the source fields.
16+
* @returns An array of `Quad` objects that represents the transformed test cases.
17+
*/
18+
export function createIndex(testCases: TestCase[], config: { baseIRI: string, projectDirectory: string }): Quad[] {
19+
const quads: Quad[] = []
20+
for (const testCase of testCases) {
21+
const transformed = transformSourceTestCase(testCase, config)
22+
quads.push(...createQuadsTestCase(transformed));
23+
}
24+
return quads
25+
}
26+
27+
/**
28+
* Transforms the source fields (e.g., policy, request, etc.) of a test case
29+
* from local file paths to URIs based on the provided base IRI.
30+
*
31+
* @param testCase - The `TestCase` object to transform.
32+
* @param baseIRI - The base IRI to replace local file paths in sources.
33+
* @returns A transformed `TestCase` object with updated source URIs.
34+
*/
35+
export function transformSourceTestCase(testCase: TestCase, config: { baseIRI: string, projectDirectory: string }): TestCase {
36+
return {
37+
identifier: testCase.identifier,
38+
policy: transformSource(testCase.policy, config),
39+
request: transformSource(testCase.request, config),
40+
stateOfTheWorld: transformSource(testCase.stateOfTheWorld, config),
41+
expectedReport: transformSource(testCase.expectedReport, config),
42+
title: testCase.title,
43+
}
44+
45+
}
46+
47+
/**
48+
* Converts a single input source (such as a policy or request) from
49+
* a local file path to a URI based on the provided base IRI.
50+
*
51+
* @param input - An object containing the source, identifier, and RDF quads.
52+
* @param baseIRI - The base IRI to replace the directory path in the source field.
53+
* @returns A transformed source object with the updated URI.
54+
*/
55+
export function transformSource(input: { identifier: string, quads: Quad[], source: string }, config: { baseIRI: string, projectDirectory: string }): { identifier: string, quads: Quad[], source: string } {
56+
const output = {
57+
identifier: input.identifier,
58+
quads: input.quads,
59+
source: input.source.replace(config.projectDirectory + '/', config.baseIRI)
60+
}
61+
return output
62+
}
63+
64+
/**
65+
* Creates RDF Quads for a test case. Converts fields such as policy, request,
66+
* state of the world, and expected report into RDF statements.
67+
*
68+
* @param testCase - The `TestCase` object for which to create RDF Quads.
69+
* @returns An array of `Quad` objects representing the test case in RDF.
70+
*/
71+
export function createQuadsTestCase(testCase: TestCase): Quad[] {
72+
/*
73+
example output:
74+
<urn:uuid:e2123eb7-0707-4f24-bcc0-9d61dd9088a9> a ex:TestCase;
75+
dct:title "Any request results into yes (Alice Request).";
76+
ex:policySource <urn:uuid:4cbd8f38-348b-4b09-8e1a-04b47c97ad78>;
77+
ex:requestSource <urn:uuid:1bafee59-006c-46a3-810c-5d176b4be364>;
78+
ex:sotwSource <urn:uuid:d63ea76e-0aed-4e4e-9a8c-0b7083ebc6e2>;
79+
ex:expectedReportSource <urn:uuid:75a52a92-5872-480e-9380-f7d9f2a53e94>.
80+
*/
81+
const quads: Quad[] = []
82+
quads.push(quad(namedNode(testCase.identifier), DCT.terms.title, literal(testCase.title)));
83+
quads.push(quad(namedNode(testCase.identifier), namedNode('http://example.org/' + 'policySource'), namedNode(testCase.policy.source)));
84+
quads.push(quad(namedNode(testCase.identifier), namedNode('http://example.org/' + 'requestSource'), namedNode(testCase.request.source)));
85+
quads.push(quad(namedNode(testCase.identifier), namedNode('http://example.org/' + 'sotwSource'), namedNode(testCase.stateOfTheWorld.source)));
86+
quads.push(quad(namedNode(testCase.identifier), namedNode('http://example.org/' + 'expectedReportSource'), namedNode(testCase.expectedReport.source)));
87+
return quads
88+
}
89+
90+
/**
91+
* Loads a hosted test case using a provided index and test case identifier.
92+
*
93+
* The method fetches and parses data sources (policy, request, and state of the world)
94+
* from their respective IRIs in the index and constructs a `TestCase` object.
95+
*
96+
* @param testCaseID - The unique identifier of the test case to load.
97+
* @param index - An array of RDF `Quad` objects representing the hosted index.
98+
* @returns A `Promise` resolving to the constructed `TestCase` object.
99+
*/
100+
export async function loadWebTestCase(testCaseID: string, index: Quad[]): Promise<TestCase> {
101+
/**
102+
* Fetches and parses RDF data from a given URL.
103+
*
104+
* The data is expected to be in plain text format but is parsed as RDF Quads.
105+
*
106+
* @param url - The URL of the hosted data to fetch.
107+
* @returns A `Promise` resolving to an array of RDF `Quad` objects.
108+
*/
109+
async function loadData(url: string): Promise<Quad[]> {
110+
const parser = new Parser()
111+
const policyResponse = await fetch(url);
112+
const policyText = await policyResponse.text();
113+
return parser.parse(policyText);
114+
}
115+
const store = new Store(index);
116+
const policyIRI = store.getObjects(testCaseID, 'http://example.org/policySource', null)[0].value;
117+
const requestIRI = store.getObjects(testCaseID, 'http://example.org/requestSource', null)[0].value;
118+
const sotwIRI = store.getObjects(testCaseID, 'http://example.org/sotwSource', null)[0].value;
119+
const reportIRI = store.getObjects(testCaseID, 'http://example.org/expectedReportSource', null)[0].value;
120+
121+
return {
122+
identifier: testCaseID,
123+
policy: {
124+
identifier: "", // could be extracted from the quads in this object
125+
quads: await loadData(policyIRI),
126+
source: policyIRI
127+
},
128+
request: {
129+
identifier: "", // could be extracted from the quads in this object
130+
quads: await loadData(requestIRI),
131+
source: requestIRI
132+
},
133+
stateOfTheWorld: {
134+
identifier: "", // could be extracted from the quads in this object
135+
quads: await loadData(sotwIRI),
136+
source: sotwIRI
137+
},
138+
expectedReport: {
139+
identifier: "", // if the quads were fetched, could be extracted from the quads in this object
140+
quads: [], // could be fetched as well, but not required
141+
source: reportIRI
142+
},
143+
title: store.getObjects(testCaseID, DCT.title, null)[0].value,
144+
}
145+
}

0 commit comments

Comments
 (0)