diff --git a/src/index.ts b/src/index.ts index f43a982..ed4a8be 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,12 +5,18 @@ import formatsPretty from "@rdfjs/formats/pretty.js"; import Serializer from "@rdfjs/serializer-turtle"; import { Validator } from "shacl-engine"; +class ValidateArguments { + path: string; + incoming: Stream; + outgoing: Writer; + report?: Writer; +} + export async function validate( - path: string, - reader: Stream, - writer: Writer, - error?: Writer, + args: ValidateArguments, ): Promise<() => Promise> { + const { path, incoming, outgoing, report } = args; + // Initialize the shared serializer. const prefixes = new PrefixMapFactory().prefixMap(); prefixes.set("ex", rdf.namedNode("http://example.org#")); @@ -22,7 +28,7 @@ export async function validate( rdf.formats.import(formatsPretty); // Create shape stream. - const res = await rdf.fetch("./tests/shacl/point.ttl"); + const res = await rdf.fetch(path); const shapes = await res.dataset(); // Parse input stream using shape stream. @@ -32,31 +38,28 @@ export async function validate( return async () => { // Anything that passes through this processor and identifies with a // specific shape should match the SHACL definition. - reader.on("data", async (data) => { + incoming.on("data", async (data) => { // Parse data into a dataset. const rawStream = Readable.from(data); const quadStream = parser.import(rawStream); const dataset = await rdf.dataset().import(quadStream); // Run through validator. - const report = await validator.validate({ dataset }); - const reportRaw = serializer.transform(report.dataset); + const result = await validator.validate({ dataset }); + const resultRaw = serializer.transform(result.dataset); // Pass through data if valid. - if (report.conforms) { - await writer.push(data); - } - - // Send report if error channel is given. - if (error) { - await error.push(reportRaw); + if (result.conforms) { + await outgoing.push(data); + } else if (report) { + await report.push(resultRaw); } }); // If the input stream closes itself, so should the output streams. - reader.on("end", () => { - writer.end(); - error?.end(); + incoming.on("end", () => { + outgoing.end(); + report?.end(); }); }; } diff --git a/tests/data/square.report.ttl b/tests/data/square.report.ttl new file mode 100644 index 0000000..e69de29 diff --git a/tests/data/square.ttl b/tests/data/square.ttl new file mode 100644 index 0000000..414182f --- /dev/null +++ b/tests/data/square.ttl @@ -0,0 +1,7 @@ +@prefix ex: . +@prefix xsd: . + +ex:SomeSquare a ex:Square; + ex:x "0"^^xsd:integer; + ex:y "0"^^xsd:integer; + ex:size "1"^^xsd:integer. diff --git a/tests/index.test.ts b/tests/index.test.ts index a786424..684cf6a 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -1,79 +1,97 @@ import { validate } from "../src"; import { SimpleStream } from "@ajuvercr/js-runner"; -import { describe, test, expect } from "vitest"; +import { describe, test, expect, beforeEach } from "vitest"; import * as fs from "fs"; -describe("shacl", () => { - // Valid point. - const valid = fs.readFileSync("./tests/data/valid.ttl").toString(); - const validReport = fs - .readFileSync("./tests/data/valid.report.ttl") - .toString(); +// Channel which streams incoming RDF. +let incoming: SimpleStream; - // Invalid point. - const invalid = fs.readFileSync("./tests/data/invalid.ttl").toString(); - const invalidReport = fs - .readFileSync("./tests/data/invalid.report.ttl") - .toString(); +// Output channel, if successful. +let outgoing: SimpleStream; +let outgoingData: string; - // SHACL data. - const shapePath = "./tests/shacl/point.ttl"; +// Reporting channel, if invalid shape is found. +let report: SimpleStream; +let reportData: string; - test("successful", async () => { - expect.assertions(2); +// Valid point. +const validRdfData = fs.readFileSync("./tests/data/valid.ttl").toString(); - // Function parameters. - const incoming = new SimpleStream(); - const outgoing = new SimpleStream(); - const report = new SimpleStream(); +// Invalid point. +const invalidRdfData = fs.readFileSync("./tests/data/invalid.ttl").toString(); +const invalidRdfReport = fs + .readFileSync("./tests/data/invalid.report.ttl") + .toString(); - outgoing.on("data", (data) => { - expect(data).toEqual(valid); - }); +const unknownRdfData = fs.readFileSync("./tests/data/square.ttl").toString(); - report.on("data", (data) => { - expect(data).toEqual(validReport); - }); +// SHACL data. +const shaclPath = "./tests/shacl/point.ttl"; - // Initialize and execute the function. - const func = await validate(shapePath, incoming, outgoing, report); - await func(); +// Utility function which waits for all streams to settle. +async function endAll(): Promise { + await incoming.end(); + await outgoing.end(); + await report.end(); +} - // Send point into the pipeline. - await incoming.push(valid); +beforeEach(async () => { + // Reset and start the streams. + incoming = new SimpleStream(); + outgoing = new SimpleStream(); + report = new SimpleStream(); - // Finish testing. - await incoming.end(); - await outgoing.end(); - await report.end(); + // Reset the data itself. + outgoingData = ""; + reportData = ""; + + // Reinitialize the data handlers. + outgoing.on("data", (data) => { + outgoingData += data; }); - test("invalid", async () => { - expect.assertions(1); + report.on("data", (data) => { + reportData += data; + }); + + // Restart the processor, which is the same for each test. + const func = await validate({ + path: shaclPath, + incoming, + outgoing, + report, + }); + await func(); +}); + +describe("shacl", () => { + test("successful", async () => { + expect.assertions(2); + + await incoming.push(validRdfData); + await endAll(); + + expect(outgoingData).toEqual(validRdfData); + expect(reportData).toEqual(""); + }); - // Function parameters. - const incoming = new SimpleStream(); - const outgoing = new SimpleStream(); - const error = new SimpleStream(); + test("invalid", async () => { + expect.assertions(2); - outgoing.on("data", () => { - expect(true).toBeFalsy(); - }); + await incoming.push(invalidRdfData); + await endAll(); - error.on("data", (data) => { - expect(data).toEqual(invalidReport); - }); + expect(outgoingData).toEqual(""); + expect(reportData).toEqual(invalidRdfReport); + }); - // Initialize and execute the function. - const func = await validate(shapePath, incoming, outgoing, error); - await func(); + test("unknown", async () => { + expect.assertions(2); - // Send point into the pipeline. - await incoming.push(invalid); + await incoming.push(unknownRdfData); + await endAll(); - // Finish testing. - await incoming.end(); - await outgoing.end(); - await error.end(); + expect(outgoingData).toEqual(unknownRdfData); + expect(reportData).toEqual(""); }); });