diff --git a/index.js b/index.js index 23ea663..23f1360 100644 --- a/index.js +++ b/index.js @@ -163,6 +163,8 @@ export * as assertions from './assertions.js'; export {generators} from './vc-generator/generators.js'; export {deriveCloned, issueCloned} from './vc-generator/issuer.js'; export {createDocLoader} from './vc-generator/documentLoader.js'; +// this is just here for backwards compatitility to avoid a major release +// FIXME remove this on next MAJOR release export { dateRegex, expectedMultibasePrefix, isObjectOrArrayOfObjects, shouldBeErrorResponse, shouldBeUrl, isStringOrArrayOfStrings, @@ -170,3 +172,4 @@ export { verificationFail } from './assertions.js'; export {createInitialVc} from './helpers.js'; +export {algorithmsSuite} from './suites/algorithms.js'; diff --git a/suites/algorithms.js b/suites/algorithms.js index 5213aef..049fe11 100644 --- a/suites/algorithms.js +++ b/suites/algorithms.js @@ -1,20 +1,43 @@ +/*! + * Copyright (c) 2024 Digital Bazaar, Inc. + */ import chai from 'chai'; import {createInitialVc} from '../helpers.js'; const expect = chai.expect; -export function algorithmsAssertions({ - endpoints, - testDescription, - vendorName, +export function algorithmsSuite({ + implemented, + testDescription = 'Data Integrity - Algorithms', credential, + features = { + authentication: false, + proofChain: false + } }) { return describe(testDescription, function() { - const columnId = testDescription; + // this will tell the report + // to make an interop matrix with this suite + this.matrix = true; + this.report = true; + this.rowLabel = 'Test Name'; + this.columnLabel = 'Implementation'; + this.implemented = []; + for(const [vendorName, {endpoints}] of implemented) { + if(!endpoints) { + throw new Error(`Expected ${vendorName} to have endpoints.`); + } + algorithmsAssert({vendorName, credential, endpoints, features}); + } + }); +} + +function algorithmsAssert({vendorName, credential, endpoints, features}) { + return describe(vendorName, function() { const [issuer] = endpoints; beforeEach(function() { this.currentTest.cell = { - columnId, + columnId: vendorName, rowId: this.currentTest.title }; }); @@ -54,78 +77,73 @@ and proof.proofPurpose values is not set, an error MUST be raised.', 'type', 'proofPurpose', 'verificationMethod'); } }); - it('If options has a non-null domain item, it MUST be equal to \ -proof.domain or an error MUST be raised.', - function() { - this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20options%20has%20a%20non%2Dnull%20domain%20item%2C%20it%20MUST%20be%20equal%20to%20proof.domain%20or%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; - this.test.cell.skipMessage = 'Pending test.'; - this.skip(); - }); - it('If options has a non-null challenge item, it MUST be equal to \ -proof.challenge or an error MUST be raised.', - function() { - this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20options%20has%20a%20non%2Dnull%20challenge%20item%2C%20it%20MUST%20be%20equal%20to%20proof.challenge%20or%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; - this.test.cell.skipMessage = 'Pending test.'; - this.skip(); - }); - it('Whenever this algorithm encodes strings, it MUST use UTF-8 encoding.', + if(features?.authentication) { + it('If options has a non-null domain item, it MUST be equal to \ + proof.domain or an error MUST be raised.', function() { - this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=(map).-,Whenever%20this%20algorithm%20encodes%20strings%2C%20it%20MUST%20use%20UTF%2D8%20encoding.,-Let%20proof%20be'; - for(const proof of proofs) { - expect(proof.proofValue.isWellFormed()).to.be.true; - } + this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20options%20has%20a%20non%2Dnull%20domain%20item%2C%20it%20MUST%20be%20equal%20to%20proof.domain%20or%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; + this.test.cell.skipMessage = 'Pending test.'; + this.skip(); }); - it('If a proof with id equal to previousProof does not exist in allProofs, \ -an error MUST be raised.', - function() { - this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20a%20proof%20with%20id%20equal%20to%20previousProofdoes%20not%20exist%20in%20allProofs%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; - for(const proof of proofs) { - if('previousProof' in proof) { - if(typeof proof.previousProof === 'string') { - proofs.some( - otherProof => otherProof.id == proof.previousProof). - should.equal(true, - 'Expected previousProof ' + - `${proof.previousProof} ` + - 'to be the id of another included proof.' - ); - } if(Array.isArray(proof.previousProof)) { - for(const previousProof in proof.previousProof) { + it('If options has a non-null challenge item, it MUST be equal to \ + proof.challenge or an error MUST be raised.', + function() { + this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20options%20has%20a%20non%2Dnull%20challenge%20item%2C%20it%20MUST%20be%20equal%20to%20proof.challenge%20or%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; + this.test.cell.skipMessage = 'Pending test.'; + this.skip(); + }); + } + if(features?.proofChain) { + it('If a proof with id equal to previousProof does not exist in ' + + 'allProofs, an error MUST be raised.', function() { + this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20a%20proof%20with%20id%20equal%20to%20previousProofdoes%20not%20exist%20in%20allProofs%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; + for(const proof of proofs) { + if('previousProof' in proof) { + if(typeof proof.previousProof === 'string') { proofs.some( - otherProof => otherProof.id == previousProof). + otherProof => otherProof.id == proof.previousProof). should.equal(true, 'Expected previousProof ' + - `${proof.previousProof} ` + - 'to be the id of another included proof.' + `${proof.previousProof} ` + + 'to be the id of another included proof.' ); + } if(Array.isArray(proof.previousProof)) { + for(const previousProof in proof.previousProof) { + proofs.some( + otherProof => otherProof.id == previousProof). + should.equal(true, + 'Expected previousProof ' + + `${proof.previousProof} ` + + 'to be the id of another included proof.' + ); + } } } } - } - }); - it('If any element of previousProof array has an id attribute \ -that does not match the id attribute of any element of allProofs, \ -an error MUST be raised.', - function() { - this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20any%20element%20of%20previousProof%20array%20has%20an%20id%20attribute%20that%20does%20not%20match%20the%20id%20attribute%20of%20any%20element%20of%20allProofs%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; - const previousProofs = []; - for(const proof of proofs) { - if('previousProof' in proof) { - if(typeof proof.previousProof === 'string') { - previousProofs.push(proof.previousProof); - } if(Array.isArray(proof.previousProof)) { - previousProofs.concat(proof.previousProof); + }); + it('If any element of previousProof array has an id attribute' + + ' that does not match the id attribute of any element of allProofs,' + + ' an error MUST be raised.', function() { + this.test.link = 'https://www.w3.org/TR/vc-data-integrity/#:~:text=If%20any%20element%20of%20previousProof%20array%20has%20an%20id%20attribute%20that%20does%20not%20match%20the%20id%20attribute%20of%20any%20element%20of%20allProofs%2C%20an%20error%20MUST%20be%20raised%20and%20SHOULD%20convey%20an%20error%20type%20of%20PROOF_GENERATION_ERROR.'; + const previousProofs = []; + for(const proof of proofs) { + if('previousProof' in proof) { + if(typeof proof.previousProof === 'string') { + previousProofs.push(proof.previousProof); + } if(Array.isArray(proof.previousProof)) { + previousProofs.concat(proof.previousProof); + } } } - } - for(const previousProof of previousProofs) { - proofs.some( - otherProof => otherProof.id == previousProof).should.equal( - true, - 'Expected all previousProof values to be the id of \ -another included proof.' - ); - } - }); + for(const previousProof of previousProofs) { + proofs.some( + otherProof => otherProof.id == previousProof).should.equal( + true, + 'Expected all previousProof values to be the id of \ + another included proof.' + ); + } + }); + } }); } diff --git a/tests/30-algorithms.js b/tests/30-algorithms.js new file mode 100644 index 0000000..6046217 --- /dev/null +++ b/tests/30-algorithms.js @@ -0,0 +1,62 @@ +/*! + * Copyright (c) 2024 Digital Bazaar, Inc. + */ +import {algorithmsSuite} from '../index.js'; +import {createSuite} from './helpers.js'; +import {cryptosuites} from './fixtures/cryptosuites.js'; +import {documentLoader} from './fixtures/documentLoader.js'; +import {MockIssuer} from './mock-data.js'; +import {versionedCredentials} from './fixtures/credentials/index.js'; + +const tag = 'Test-Issuer-Valid'; +const tags = [tag]; + +describe('should run with all suites', function() { + for(const testDataOptions of cryptosuites) { + for(const [ + vcVersion, + {credential, mandatoryPointers} + ] of versionedCredentials) { + _runSuite({ + vcVersion, + testDataOptions, + credential, + mandatoryPointers + }); + } + } +}); + +function _runSuite({ + vcVersion, testDataOptions, + credential, mandatoryPointers +}) { + const {suiteName, keyType = ''} = testDataOptions; + let title = `VC ${vcVersion} Suite ${suiteName}`; + if(keyType) { + title += `keyType ${keyType}`; + } + return describe(title, + function() { + const implemented = new Map(); + const {cryptosuite, key, derived} = testDataOptions; + const signer = key.signer(); + const suite = createSuite({ + signer, cryptosuite, + mandatoryPointers, derived + }); + // pass the VC's context to the issuer + const {'@context': contexts} = credential; + const issuer = new MockIssuer({ + tags, suite, + contexts, documentLoader + }); + implemented.set(suiteName, {endpoints: [issuer]}); + algorithmsSuite({ + implemented, + tag, + credential, + cryptosuiteName: suiteName, + }); + }); +}