From cab34a3e2146f844fdb1f602563c864cec9fa3d0 Mon Sep 17 00:00:00 2001 From: Julian Rojas Date: Thu, 2 Nov 2023 17:01:59 +0100 Subject: [PATCH] Add functional tests for processor functions --- bun.lockb | Bin 123629 -> 132774 bytes package.json | 1 + processors.ttl | 26 ++--- src/rml/rml.ts | 226 ++++++++++++++++++++++++------------------- src/voc.ts | 8 +- test/rml.test.ts | 212 ++++++++++++++++++++++++++++++++++++++++ test/yarrrml.test.ts | 46 +++++++++ 7 files changed, 407 insertions(+), 112 deletions(-) create mode 100644 test/rml.test.ts create mode 100644 test/yarrrml.test.ts diff --git a/bun.lockb b/bun.lockb index b7f5b4b43b8fc075e6cef41325d3e499b2e067ef..b735694bd58d0a48275d5cf67f92f6f6a9af1246 100755 GIT binary patch delta 21945 zcmeHvcYIB0yY^aJc4Q+3AsshG*b(g2!_fxah+Rx*I6$>YsO>uAJ<}
  • vH`5TG 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 VTX|RE+f95|O1-t#7XGB`e*x#%<)Q!p delta 16783 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 diff --git a/package.json b/package.json index 4082c48..a3af31c 100644 --- a/package.json +++ b/package.json @@ -33,6 +33,7 @@ "@jest/globals": "^29.7.0", "@types/n3": "^1.16.3", "@types/node": "^20.8.10", + "del": "^7.1.0", "ts-node": "^10.9.1", "typescript": "^5.2.2" } diff --git a/processors.ttl b/processors.ttl index 718144d..5e5fb6c 100644 --- a/processors.ttl +++ b/processors.ttl @@ -49,15 +49,15 @@ js:RMLMapperReader a js:JsProcess; a fno:Mapping; fno:parameterMapping [ a fnom:PositionParameterMapping; - fnom:functionParameter "sources"; + fnom:functionParameter "mappingInput"; fnom:implementationParameterPosition "0"^^xsd:integer; ], [ a fnom:PositionParameterMapping; - fnom:functionParameter "targets"; + fnom:functionParameter "sources"; fnom:implementationParameterPosition "1"^^xsd:integer; ], [ a fnom:PositionParameterMapping; - fnom:functionParameter "mappingInput"; + fnom:functionParameter "targets"; fnom:implementationParameterPosition "2"^^xsd:integer; ], [ a fnom:PositionParameterMapping; @@ -110,28 +110,32 @@ js:RMLMapperReader a js:JsProcess; [ ] a sh:NodeShape; sh:targetClass js:RMLMapperReader; sh:property [ + sh:path js:mappings; + sh:name "mappingInput"; + sh:class :ReaderChannel; + sh:minCount 1; + sh:maxCount 1 + ], [ sh:path js:rmlSource; sh:name "sources"; sh:class js:RmlSource; + sh:minCount 0 ], [ sh:path js:rmlTarget; sh:name "targets"; sh:class js:RmlTarget; - ], [ - sh:path js:mappings; - sh:name "mappingInput"; - sh:class :ReaderChannel; - sh:maxCount 1; - sh:minCount 1; + sh:minCount 0 ], [ sh:path js:output; sh:name "defaultWriter"; sh:class :WriterChannel; - sh:maxCount 1; + sh:minCount 1; + sh:maxCount 1 ], [ sh:path js:rmlJar; sh:name "jarLocation"; sh:datatype xsd:string; - sh:maxCount 1; + sh:minCount 0; + sh:maxCount 1 ]. diff --git a/src/rml/rml.ts b/src/rml/rml.ts index 7ccca2d..0a9e402 100644 --- a/src/rml/rml.ts +++ b/src/rml/rml.ts @@ -37,9 +37,9 @@ export type Target = { }; export async function rmlMapper( - sources: Source[], - targets: Target[], mappingReader: Stream, + sources?: Source[], + targets?: Target[], defaultWriter?: Writer, jarLocation?: string, ) { @@ -47,17 +47,21 @@ export async function rmlMapper( const outputFile = "/tmp/rml-" + uid + "-output.ttl"; // Iterate over declared logical sources and organize them in a temporal location - for (let source of sources) { - const filename = source.location.split("/").pop(); - source.newLocation = `/tmp/rml-${uid}-input-${randomUUID()}-${filename}`; - source.hasData = false; - source.trigger = !!source.trigger; + if (sources) { + for (let source of sources) { + const filename = source.location.split("/").pop(); + source.newLocation = `/tmp/rml-${uid}-input-${randomUUID()}-${filename}`; + source.hasData = false; + source.trigger = !!source.trigger; + } } // Iterate over declared logical targets and organize them in a temporal location - for (let target of targets) { - const filename = target.location.split("/").pop(); - target.newLocation = `/tmp/rml-${uid}-output-${randomUUID()}-${filename}`; + if (targets) { + for (let target of targets) { + const filename = target.location.split("/").pop(); + target.newLocation = `/tmp/rml-${uid}-output-${randomUUID()}-${filename}`; + } } const jarFile = await getJarFile(jarLocation, false, RML_MAPPER_RELEASE); @@ -77,31 +81,45 @@ export async function rmlMapper( console.error("Could not map incoming rml input"); console.error(ex); } - }).on("end", () => { + }).on("end", async () => { // We assume mappings to be static and only proceed to execute them once we have them all - for (let source of sources) { - console.log("handling source", source.location); - // Process raw data input streams - source.dataInput.data(async (data) => { - console.log("Got data for ", source.location); - source.hasData = true; - await writeFile(source.newLocation, data); - - if (sources.every((x) => x.hasData)) { - // We made sure that all declared logical sources are present - console.log("Start mapping now"); - await executeMappings( - sources, - mappingLocations, - jarFile, - outputFile, - targets, - defaultWriter - ); - } else { - console.error("Cannot start mapping, not all data has been received"); - } - }); + if (sources) { + for (let source of sources) { + console.log("handling source", source.location); + // Process raw data input streams + source.dataInput.data(async (data) => { + console.log("Got data for ", source.location); + source.hasData = true; + await writeFile(source.newLocation, data); + + if (sources.every((x) => x.hasData)) { + // We made sure that all declared logical sources are present + console.log("Start mapping now"); + await executeMappings( + mappingLocations, + jarFile, + outputFile, + sources, + targets, + defaultWriter + ); + } else { + console.error("Cannot start mapping, not all data has been received"); + } + }); + } + } else { + // No declared logical sources means that raw data access is delegated to the RML engine. + // For example, as in the case of remote RDBs or HTTP APIs + console.log("Start mapping now"); + await executeMappings( + mappingLocations, + jarFile, + outputFile, + sources, + targets, + defaultWriter + ); } }); @@ -120,7 +138,7 @@ function randomUUID(length = 8) { return result; } -function transformMapping(input: string, sources: Source[], targets: Target[],) { +function transformMapping(input: string, sources?: Source[], targets?: Target[],) { const quads = new Parser().parse(input); const store = new Store(quads); @@ -135,41 +153,43 @@ function transformMapping(input: string, sources: Source[], targets: Target[],) const extractTarget = pred(RMLT.terms.target).one().then(targetLens2); - const targetLens = match(undefined, RDF.terms.type, RML.terms.LogicalTarget) + const targetLens = match(undefined, RDF.terms.type, RMLT.terms.LogicalTarget) .thenAll(subject) .thenAll(extractTarget); const foundTargets = targetLens.execute(quads); console.log("Found targets", foundTargets); for (let foundTarget of foundTargets) { - let found = false; - for (let target of targets) { - if (target.location === foundTarget.target.value) { - console.log( - "Moving location", - foundTarget.target.value, - "to", - target.newLocation, - ); - found = true; - // Remove the old location - store.removeQuad( - foundTarget.subject, - VOID.terms.dataDump, - foundTarget.target, - ); - - // Add the new location - store.addQuad( - foundTarget.subject, - VOID.terms.dataDump, - literal("file://" + target.newLocation), - ); - break; + if (targets) { + let found = false; + for (let target of targets) { + if (target.location === foundTarget.target.value) { + console.log( + "Moving location", + foundTarget.target.value, + "to", + target.newLocation, + ); + found = true; + // Remove the old location + store.removeQuad( + foundTarget.subject, + VOID.terms.dataDump, + foundTarget.target, + ); + + // Add the new location + store.addQuad( + foundTarget.subject, + VOID.terms.dataDump, + literal("file://" + target.newLocation), + ); + break; + } + } + if (!found) { + throw `Logical source ${foundTarget.subject.value} has no configured source`; } - } - if (!found) { - throw `Logical source ${foundTarget.subject.value} has no configured source`; } } @@ -196,34 +216,36 @@ function transformMapping(input: string, sources: Source[], targets: Target[],) // There exists a source that has no defined source, we cannot map this mapping for (let foundSource of foundSources) { - let found = false; - for (let source of sources) { - if (source.location === foundSource.source) { - console.log( - "Moving location", - foundSource.source, - "to", - source.newLocation, - ); - found = true; - // Remove the old location - store.removeQuad( - foundSource.subject, - RML.terms.source, - literal(foundSource.source), - ); - - // Add the new location - store.addQuad( - foundSource.subject, - RML.terms.source, - literal(source.newLocation), - ); - break; + if (sources) { + let found = false; + for (let source of sources) { + if (source.location === foundSource.source) { + console.log( + "Moving location", + foundSource.source, + "to", + source.newLocation, + ); + found = true; + // Remove the old location + store.removeQuad( + foundSource.subject, + RML.terms.source, + literal(foundSource.source), + ); + + // Add the new location + store.addQuad( + foundSource.subject, + RML.terms.source, + literal(source.newLocation), + ); + break; + } + } + if (!found) { + throw `Logical source ${foundSource.subject.value} has no configured source (${foundSource.source}) channel!`; } - } - if (!found) { - throw `Logical source ${foundSource.subject.value} has no configured source (${foundSource.source}) channel!`; } } @@ -231,18 +253,20 @@ function transformMapping(input: string, sources: Source[], targets: Target[],) } async function executeMappings( - sources: Source[], mappingLocations: string[], jarFile: string, outputFile: string, - targets: Target[], + sources?: Source[], + targets?: Target[], defaultWriter?: Writer ) { - for (let source of sources) { - // Reset the hasData property so it requires new data before it can map again - // Useful when multiple sources need to update - if (source.trigger) { - source.hasData = false; + if (sources) { + for (let source of sources) { + // Reset the hasData property so it requires new data before it can map again + // Useful when multiple sources need to update + if (source.trigger) { + source.hasData = false; + } } } @@ -262,9 +286,11 @@ async function executeMappings( out += await readFile(outputFile, { encoding: "utf8" }); - for (let target of targets) { - const file = await readFile(target.newLocation, { encoding: "utf8" }); - await target.writer.push(file); + if (targets) { + for (let target of targets) { + const file = await readFile(target.newLocation, { encoding: "utf8" }); + await target.writer.push(file); + } } console.log("Done", mappingFile); } diff --git a/src/voc.ts b/src/voc.ts index 4dde29e..76f671f 100644 --- a/src/voc.ts +++ b/src/voc.ts @@ -1,5 +1,7 @@ import { createUriAndTermNamespace } from "@treecg/types"; +export * from "@treecg/types"; + export const VOID = createUriAndTermNamespace( "http://rdfs.org/ns/void#", "dataDump", @@ -16,7 +18,7 @@ export const RML = createUriAndTermNamespace( "referenceFormulation", "reference", "iterator", - "LogicalTarget", + "LogicalSource" ); export const RMLS = createUriAndTermNamespace( @@ -31,6 +33,7 @@ export const RMLS = createUriAndTermNamespace( export const RMLT = createUriAndTermNamespace( "http://semweb.mmlab.be/ns/rml-target#", + "LogicalTarget", "serialization", "target", ); @@ -38,6 +41,9 @@ export const RMLT = createUriAndTermNamespace( export const RR = createUriAndTermNamespace( "http://www.w3.org/ns/r2rml#", "TriplesMap", + "SubjectMap", + "PredicateObjectMap", + "GraphMap", "constant", "termType", "IRI", diff --git a/test/rml.test.ts b/test/rml.test.ts new file mode 100644 index 0000000..5799f6e --- /dev/null +++ b/test/rml.test.ts @@ -0,0 +1,212 @@ +import { describe, test, expect, afterAll } from "@jest/globals"; +import { SimpleStream } from "@ajuvercr/js-runner"; +import { Parser, Store } from "n3"; +import { deleteAsync } from "del"; +import { rmlMapper, Source, Target } from "../src/rml/rml"; +import { RDF, RDFS } from "../src/voc"; + +describe("Functional tests for the rmlMapper Connector Architecture function", async () => { + const prefixes = ` +@prefix rdfs: . +@prefix xsd: . +@prefix rr: . +@prefix rml: . +@prefix rmlt: . +@prefix ql: . +@prefix void: . +@prefix formats: . +@prefix td: . +@prefix htv: . +@prefix hctl: . +@prefix ex: .`; + + test("Mapping process with declared logical sources and targets", async () => { + const rmlDoc = ` +${prefixes} +ex:map_test-mapping_000 a rr:TriplesMap ; + rdfs:label "test-mapping" ; + rml:logicalSource [ + a rml:LogicalSource ; + rml:source "dataset/data.xml" ; + rml:iterator "//data" ; + rml:referenceFormulation ql:XPath + ] ; + rr:subjectMap [ + a rr:SubjectMap ; + rr:template "http://example.org/{@id}" ; + rml:logicalTarget [ + a rmlt:LogicalTarget ; + rmlt:serialization formats:N-Quads ; + rmlt:target [ + a void:Dataset ; + void:dataDump + ] + ] ; + rr:graphMap [ + a rr:GraphMap ; + rr:constant "http://example.org/myNamedGraph" + ] + ] ; + rr:predicateObjectMap [ + a rr:PredicateObjectMap ; + rr:predicateMap [ + a rr:PredicateMap ; + rr:constant "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" + ] ; + rr:objectMap [ + a rr:ObjectMap ; + rr:constant ; + rr:termType rr:IRI + ] + ] ; + rr:predicateObjectMap [ + a rr:PredicateObjectMap ; + rr:predicateMap [ + a rr:PredicateMap ; + rr:constant rdfs:label + ] ; + rr:objectMap [ + a rr:ObjectMap ; + rml:reference "@label" ; + rr:termType rr:Literal + ] + ] .`; + + const rawData = ` + + + + + `; + // Define function parameters + const mappingReader = new SimpleStream(); + const sourceInputStream = new SimpleStream(); + const targetOutputStream = new SimpleStream(); + const sources: Source[] = [ + { + location: "dataset/data.xml", + newLocation: "", + dataInput: sourceInputStream, + hasData: false, + trigger: false + } + ]; + const targets: Target[] = [ + { + location: "file:///results/output.nq", + newLocation: "", + writer: targetOutputStream + } + ]; + + // Check output + targetOutputStream.data((data: string) => { + const store = new Store(); + store.addQuads(new Parser().parse(data)); + + expect(store.getQuads(null, null, null, null).length).toBe(4); + expect(store.getQuads( + "http://example.org/001", + RDF.type, + null, + "http://example.org/myNamedGraph").length + ).toBe(1); + expect(store.getQuads( + "http://example.org/002", + RDFS.label, + null, + "http://example.org/myNamedGraph").length + ).toBe(1); + }); + + // Execute function + await rmlMapper(mappingReader, sources, targets, undefined, "/tmp/rmlMapper.jar"); + + // Push mappings input data + await mappingReader.push(rmlDoc); + await mappingReader.end(); + + // Push raw input data + await sourceInputStream.push(rawData); + }); + + test("Mapping process without declared logical sources and default output", async () => { + const rmlDoc = ` +${prefixes} +ex:map_test-mapping_000 a rr:TriplesMap ; + rdfs:label "test-mapping" ; + rml:logicalSource [ + a rml:LogicalSource ; + rml:source [ + a td:PropertyAffordance ; + td:hasForm [ + a td:Form ; + hctl:hasTarget "https://api.blue-bike.be/pub/location" ; + hctl:forContentType "application/json" ; + hctl:hasOperationType td:readproperty ; + htv:methodName "GET" ; + htv:headers ([ + htv:fieldName "User-Agent" ; + htv:fieldValue "IDLab - Ghent University - imec (RMLMapper)" + ]); + ] + ] ; + rml:referenceFormulation ql:JSONPath ; + rml:iterator "$.[*]" + ] ; + rr:subjectMap [ + a rr:SubjectMap ; + rr:template "https://blue-bike.be/stations/{id}" ; + rr:class ex:BicycleParkingStation + ] ; + rr:predicateObjectMap [ + a rr:PredicateObjectMap ; + rr:predicateMap [ + a rr:PredicateMap ; + rr:constant ex:name + ] ; + rr:objectMap [ + a rr:ObjectMap ; + rml:reference "name" ; + rr:datatype xsd:string + ] + ] ; + rr:predicateObjectMap [ + a rr:PredicateObjectMap ; + rr:predicateMap [ + a rr:PredicateMap ; + rr:constant ex:availableBikes + ] ; + rr:objectMap [ + a rr:ObjectMap ; + rml:reference "bikes_available" ; + rr:datatype xsd:integer + ] + ] .`; + // Define function parameters + const mappingReader = new SimpleStream(); + const outputStream = new SimpleStream(); + + outputStream.data(data => { + const store = new Store(); + store.addQuads(new Parser().parse(data)); + + expect(store.getQuads(null, RDF.type, null, null).length).toBeGreaterThan(0); + expect(store.getQuads(null, "http://example.org/name", null, null).length).toBeGreaterThan(0); + expect(store.getQuads(null, "http://example.org/availableBikes", null, null).length).toBeGreaterThan(0); + }); + + // Execute function + await rmlMapper(mappingReader, undefined, undefined, outputStream, "/tmp/rmlMapper.jar"); + + // Push mappings input data + await mappingReader.push(rmlDoc); + await mappingReader.end(); + + }); +}); + +afterAll(async () => { + // Clean up temporal files + await deleteAsync(["/tmp/rml*"], { force: true }); +}); \ No newline at end of file diff --git a/test/yarrrml.test.ts b/test/yarrrml.test.ts new file mode 100644 index 0000000..1be8045 --- /dev/null +++ b/test/yarrrml.test.ts @@ -0,0 +1,46 @@ +import { describe, test, expect } from "@jest/globals"; +import { SimpleStream } from "@ajuvercr/js-runner"; +import { yarrrml2rml } from "../src/yarrrml/yarrrml"; +import { Parser, Store } from "n3"; +import { RDF, RML, RR } from "../src/voc"; + +describe("Functional tests for the yarrrml2rml Connector Architecture function", async () => { + const yarrrmlDoc = ` +prefixes: + ex: "http://example.org/" + rdfs: "http://www.w3.org/2000/01/rdf-schema#" + +mappings: + test-mapping: + sources: + - ["dataset/data.xml~xpath","/data"] + s: ex:$(@id) + po: + - [a, ex:Entity] + - [rdfs:label, $(@label)] + graph: ex:myNamedGraph`; + + test("Given a YARRRML document it produces RML triples", async () => { + const reader = new SimpleStream(); + const writer = new SimpleStream(); + + writer.data((data: string) => { + const store = new Store(); + store.addQuads(new Parser().parse(data)); + + // Check that we got RML triples + expect(store.getQuads(null, RDF.type, RML.LogicalSource, null).length).toBeGreaterThan(0); + expect(store.getQuads(null, RDF.type, RR.TriplesMap, null).length).toBeGreaterThan(0); + expect(store.getQuads(null, RDF.type, RR.SubjectMap, null).length).toBeGreaterThan(0); + expect(store.getQuads(null, RDF.type, RR.PredicateObjectMap, null).length).toBeGreaterThan(0); + expect(store.getQuads(null, RDF.type, RR.GraphMap, null).length).toBeGreaterThan(0); + }); + + // Execute function + yarrrml2rml(reader, writer); + + // Push some data to the input stream + await reader.push(yarrrmlDoc); + await reader.end(); + }); +}); \ No newline at end of file