From c58827be87bc2eb43bc8c32cd6785037b86c3e8f Mon Sep 17 00:00:00 2001 From: "David I. Lehn" Date: Tue, 19 Sep 2023 15:42:37 -0400 Subject: [PATCH] Test and coverage updates. - Fix maxWorkFactor === 0 use case. - Improve error if N-Quads input not a string. - Fix calls without options. - Test error messages to ensure correct errors are thrown. - Remove default options not needed for testing. - Add various tests for complexity controls, edge cases, and coverage. - Add simple duplicate quads test. --- lib/URDNA2015.js | 2 +- lib/index.js | 7 ++- test/misc.js | 150 ++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 140 insertions(+), 19 deletions(-) diff --git a/lib/URDNA2015.js b/lib/URDNA2015.js index a4f2ef1..a94f340 100644 --- a/lib/URDNA2015.js +++ b/lib/URDNA2015.js @@ -103,7 +103,7 @@ module.exports = class URDNA2015 { if(this.maxDeepIterations < 0) { // calculate maxDeepIterations if not explicit if(this.maxWorkFactor === 0) { - // use 0 default + this.maxDeepIterations = 0; } else if(this.maxWorkFactor === Infinity) { this.maxDeepIterations = Infinity; } else { diff --git a/lib/index.js b/lib/index.js index 4339813..4d246ce 100644 --- a/lib/index.js +++ b/lib/index.js @@ -47,6 +47,9 @@ try { function _inputToDataset(input, options) { if(options.inputFormat) { if(options.inputFormat === 'application/n-quads') { + if(typeof input !== 'string') { + throw new Error('N-Quads input must be a string.'); + } return exports.NQuads.parse(input); } throw new Error( @@ -141,7 +144,7 @@ exports._rdfCanonizeNative = function(api) { * @returns {Promise} - A Promise that resolves to the canonicalized * RDF Dataset. */ -exports.canonize = async function(input, options) { +exports.canonize = async function(input, options = {}) { const dataset = _inputToDataset(input, options); _checkOutputFormat(options); @@ -229,7 +232,7 @@ exports.canonize = async function(input, options) { * @returns {Promise} - A Promise that resolves to the canonicalized * RDF Dataset. */ -exports._canonizeSync = function(input, options) { +exports._canonizeSync = function(input, options = {}) { const dataset = _inputToDataset(input, options); _checkOutputFormat(options); diff --git a/test/misc.js b/test/misc.js index feedcef..7c1dee5 100644 --- a/test/misc.js +++ b/test/misc.js @@ -16,6 +16,8 @@ describe('API tests', () => { error = e; } assert(error); + assert.match(error.message, + /No RDF Dataset Canonicalization algorithm specified./); }); it('should reject invalid algorithm', async () => { @@ -28,6 +30,8 @@ describe('API tests', () => { error = e; } assert(error); + assert.match(error.message, + /Invalid RDF Dataset Canonicalization algorithm/); }); it('should reject invalid inputFormat', async () => { @@ -35,13 +39,13 @@ describe('API tests', () => { try { await rdfCanonize.canonize('', { algorithm: 'RDFC-1.0', - inputFormat: 'application/bogus', - format: 'application/n-quads' + inputFormat: 'application/bogus' }); } catch(e) { error = e; } assert(error); + assert.match(error.message, /Unknown canonicalization input format/); }); it('should handle falsy inputFormat', async () => { @@ -67,6 +71,7 @@ describe('API tests', () => { error = e; } assert(error); + assert.match(error.message, /Unsupported algorithm/); }); it('should reject invalid output format', async () => { @@ -80,6 +85,17 @@ describe('API tests', () => { error = e; } assert(error); + assert.match(error.message, /Unknown canonicalization output format/); + }); + + it('should handle valid output format', async () => { + const input = []; + const expected = ''; + const output = await rdfCanonize.canonize(input, { + algorithm: 'RDFC-1.0', + format: 'application/n-quads' + }); + assert.deepStrictEqual(output, expected); }); it('should handle falsy output format', async () => { @@ -98,13 +114,13 @@ describe('API tests', () => { try { await rdfCanonize.canonize([], { algorithm: 'RDFC-1.0', - inputFormat: 'application/bogus', - format: 'application/n-quads' + inputFormat: 'application/n-quads' }); } catch(e) { error = e; } assert(error); + assert.match(error.message, /N-Quads input must be a string./); }); it('should set canonicalIdMap data', async () => { @@ -125,7 +141,6 @@ _:c14n1 "v1" . const output = await rdfCanonize.canonize(input, { algorithm: 'RDFC-1.0', inputFormat: 'application/n-quads', - format: 'application/n-quads', canonicalIdMap }); assert.deepStrictEqual(output, expected); @@ -134,8 +149,7 @@ _:c14n1 "v1" . it('should allow URDNA2015 by default', async () => { await rdfCanonize.canonize([], { - algorithm: 'URDNA2015', - format: 'application/n-quads' + algorithm: 'URDNA2015' }); }); @@ -144,13 +158,14 @@ _:c14n1 "v1" . try { await rdfCanonize.canonize([], { algorithm: 'URDNA2015', - format: 'application/n-quads', rejectURDNA2015: true }); } catch(e) { error = e; } assert(error); + assert.match(error.message, + /Invalid RDF Dataset Canonicalization algorithm/); }); it('should abort (timeout)', async () => { @@ -163,7 +178,6 @@ _:c14n1 "v1" . output = await rdfCanonize.canonize(data, { algorithm: 'RDFC-1.0', inputFormat: 'application/n-quads', - format: 'application/n-quads', signal: AbortSignal.timeout(100), maxDeepIterations: Infinity }); @@ -171,13 +185,36 @@ _:c14n1 "v1" . error = e; } assert(error, 'no abort error'); + assert.match(error.message, /Abort signal received/); + assert.match(error.message, /TimeoutError/); assert(!output, 'abort should have no output'); }); - it('should abort (work factor)', async () => { + it('should abort (work factor = 0)', async () => { const {data} = graphs.makeDataA({ - subjects: 6, - objects: 6 + subjects: 2, + objects: 2 + }); + let error; + let output; + try { + output = await rdfCanonize.canonize(data, { + algorithm: 'RDFC-1.0', + inputFormat: 'application/n-quads', + signal: null, + maxWorkFactor: 0 + }); + } catch(e) { + error = e; + } + assert(error, 'no abort error'); + assert(!output, 'abort should have no output'); + }); + + it('should abort (work factor = 1)', async () => { + const {data} = graphs.makeDataA({ + subjects: 3, + objects: 3 }); let error; let output; @@ -185,7 +222,6 @@ _:c14n1 "v1" . output = await rdfCanonize.canonize(data, { algorithm: 'RDFC-1.0', inputFormat: 'application/n-quads', - format: 'application/n-quads', signal: null, maxWorkFactor: 1 }); @@ -196,7 +232,74 @@ _:c14n1 "v1" . assert(!output, 'abort should have no output'); }); - it('should abort (iterations)', async () => { + it('should not abort (work factor = Infinity)', async () => { + const {data} = graphs.makeDataA({ + subjects: 3, + objects: 3 + }); + await rdfCanonize.canonize(data, { + algorithm: 'RDFC-1.0', + inputFormat: 'application/n-quads', + maxWorkFactor: Infinity + }); + }); + + it('should not abort (work factor = 1, max iterations set)', async () => { + const {data} = graphs.makeDataA({ + subjects: 3, + objects: 3 + }); + await rdfCanonize.canonize(data, { + algorithm: 'RDFC-1.0', + inputFormat: 'application/n-quads', + maxWorkFactor: 1, + maxDeepIterations: 33 + }); + }); + + it('should abort (iterations [max deep = 0])', async () => { + const {data} = graphs.makeDataA({ + subjects: 2, + objects: 2 + }); + let error; + let output; + try { + output = await rdfCanonize.canonize(data, { + algorithm: 'RDFC-1.0', + inputFormat: 'application/n-quads', + maxDeepIterations: 0 + }); + } catch(e) { + error = e; + } + assert(error, 'no abort error'); + assert.match(error.message, /Maximum deep iterations exceeded/); + assert(!output, 'abort should have no output'); + }); + + it('should abort (iterations [max work factor = 0])', async () => { + const {data} = graphs.makeDataA({ + subjects: 2, + objects: 2 + }); + let error; + let output; + try { + output = await rdfCanonize.canonize(data, { + algorithm: 'RDFC-1.0', + inputFormat: 'application/n-quads', + maxWorkFactor: 0 + }); + } catch(e) { + error = e; + } + assert(error, 'no abort error'); + assert.match(error.message, /Maximum deep iterations exceeded/); + assert(!output, 'abort should have no output'); + }); + + it('should abort (iterations [max deep = 1000])', async () => { const {data} = graphs.makeDataA({ subjects: 6, objects: 6 @@ -207,14 +310,13 @@ _:c14n1 "v1" . output = await rdfCanonize.canonize(data, { algorithm: 'RDFC-1.0', inputFormat: 'application/n-quads', - format: 'application/n-quads', - signal: null, maxDeepIterations: 1000 }); } catch(e) { error = e; } assert(error, 'no abort error'); + assert.match(error.message, /Maximum deep iterations exceeded/); assert(!output, 'abort should have no output'); }); @@ -277,4 +379,20 @@ _:b0 _:b1 _:b2 . assert.deepStrictEqual(output, expected); }); + it('should handle duplicate quads', async () => { + const input = `\ +_:b0 _:b1 . +_:b0 _:b1 . +`; + const expected = `\ +_:c14n1 _:c14n0 . +`; + + const output = await rdfCanonize.canonize(input, { + algorithm: 'RDFC-1.0', + inputFormat: 'application/n-quads' + }); + assert.deepStrictEqual(output, expected); + }); + });