diff --git a/documentation/docs/test-suites/technical-interoperability/untp-extensions/qr-link-encryption.md b/documentation/docs/test-suites/technical-interoperability/untp-extensions/qr-link-encryption.md index fe33986a..b2178bc5 100644 --- a/documentation/docs/test-suites/technical-interoperability/untp-extensions/qr-link-encryption.md +++ b/documentation/docs/test-suites/technical-interoperability/untp-extensions/qr-link-encryption.md @@ -31,6 +31,11 @@ To test your QR Link / Encryption implementation, follow these steps: headers: {}, method: 'GET', }, + QrLinkUnencrypted: { + url: 'https://example.com/credential-verifier?q=%7B%22payload%22%3A%7B%22uri%22%3A%22https%3A%2F%2Fapi.vckit.showthething.com%2Fencrypted-storage%2Fencrypted-data%2F0a6031a9-2740-49cd-b12b-1ed02820f01d%22%7D%7D', + headers: {}, + method: 'GET', + }, }, }; ``` diff --git a/packages/untp-test-suite/jest.config.js b/packages/untp-test-suite/jest.config.js index ea2315dd..d9033500 100644 --- a/packages/untp-test-suite/jest.config.js +++ b/packages/untp-test-suite/jest.config.js @@ -6,7 +6,14 @@ export default { rootDir: './', moduleFileExtensions: [...defaults.moduleFileExtensions, 'mts'], collectCoverage: false, - collectCoverageFrom: ['!**/examples/**', '!**/types/**', '!**/build/**', '!**/node_modules/**', '!**/**/index.ts'], + collectCoverageFrom: [ + '!**/examples/**', + '!**/types/**', + '!**/build/**', + '!**/node_modules/**', + '!**/**/index.ts', + '/src/**/*.ts', + ], coverageReporters: ['text', 'lcov', 'json', 'json-summary'], coverageProvider: 'v8', coverageDirectory: './coverage', diff --git a/packages/vc-test-suite/README.md b/packages/vc-test-suite/README.md index db5c6e81..5595fd26 100644 --- a/packages/vc-test-suite/README.md +++ b/packages/vc-test-suite/README.md @@ -8,10 +8,14 @@ yarn install ## Setup -There are two parts of the test suite: `Render Template 2024` and `QR Link Encryption`. To add your implementation to this test suite you will need to add 2 endpoints to your implementation manifest in `config.ts`: +There are three parts of the test suite: `Render Template 2024`, `QR Link Verification` and `Storage`. To add your implementation to this test suite you will need to add some endpoints to your implementation manifest in `config.ts`: - A Render Template 2024 endpoint in the `RenderTemplate2024` property. - An Encrypted QR Link in the `QrLinkEncrypted` property. +- An Unencrypted QR Link in the `QrLinkUnencrypted` property. +- A Storage endpoint in the `Storage` property. + +> **IMPORTANT**: Please change the url in the `config.ts` file to match your implementation. ## Usage @@ -40,3 +44,8 @@ Open the generated report HTML file in the reports folder to check the results. ```bash yarn test:package ``` + +### Additional Documentation + +- [Technical Interoperability](https://uncefact.github.io/tests-untp/docs/test-suites/technical-interoperability/) +- [Verify Link](https://uncefact.github.io/tests-untp/docs/mock-apps/common/verify-link) diff --git a/packages/vc-test-suite/__tests__/QrLinkVerification/helper.test.ts b/packages/vc-test-suite/__tests__/QrLinkVerification/helper.test.ts new file mode 100644 index 00000000..87410a15 --- /dev/null +++ b/packages/vc-test-suite/__tests__/QrLinkVerification/helper.test.ts @@ -0,0 +1,60 @@ +import { parseQRLink, isURLEncoded } from '../../tests/QrLinkVerification/helper'; + +describe('Helper Functions', () => { + describe('parseQRLink', () => { + it('should parse the QR code link correctly', () => { + const qrcodeLink = + 'https://example.com/verify?q=%7B%22payload%22%3A%7B%22uri%22%3A%22https%3A%2F%2Fexample.com%2Fcredential%22%2C%22key%22%3A%22some-key%22%2C%22hash%22%3A%22some-hash%22%7D%7D'; + const result = parseQRLink(qrcodeLink); + + expect(result).toEqual({ + verify_app_address: 'https://example.com/verify', + q: { + payload: { + uri: 'https://example.com/credential', + key: 'some-key', + hash: 'some-hash', + }, + }, + }); + }); + + it('should handle missing query parameter gracefully', () => { + JSON.parse = jest.fn().mockImplementation((value) => { + return { + payload: { + uri: undefined, + key: undefined, + hash: undefined, + }, + }; + }); + + const qrcodeLink = 'https://example.com/verify'; + const result = parseQRLink(qrcodeLink); + + expect(result).toEqual({ + verify_app_address: 'https://example.com/verify', + q: { + payload: { + uri: undefined, + key: undefined, + hash: undefined, + }, + }, + }); + }); + }); + + describe('isURLEncoded', () => { + it('should return true for URL encoded strings', () => { + expect(isURLEncoded('%20')).toBe(true); + expect(isURLEncoded('%7B')).toBe(true); + }); + + it('should return false for non-URL encoded strings', () => { + expect(isURLEncoded(' ')).toBe(false); + expect(isURLEncoded('{')).toBe(false); + }); + }); +}); diff --git a/packages/vc-test-suite/config.ts b/packages/vc-test-suite/config.ts index 4ed808e8..e8d9c889 100644 --- a/packages/vc-test-suite/config.ts +++ b/packages/vc-test-suite/config.ts @@ -1,8 +1,13 @@ export default { - implementationName: 'VCkit', + implementationName: 'Tier 1 Test Suite', testSuites: { QrLinkEncrypted: { - url: 'http://localhost:3001/verify?q=%7B%22payload%22%3A%7B%22uri%22%3A%22http%3A%2F%2Flocalhost%3A3334%2Fv1%2Fverifiable-credentials%2Fc2eb5fee-da3b-411b-9b03-e8f7fb20dc3d.json%22%2C%22key%22%3A%22e5e0d20e35e2786773720cbba011ca26cca0d6fd279a6c3fd00980a5ed1fb5c8%22%2C%22hash%22%3A%2231e96f8b6896b9fc6f9b86267574b2926b5637e1f65027b437119cfd2033f3d7%22%7D%7D', + url: 'http://localhost:3003/verify?q=%7B%22payload%22%3A%7B%22uri%22%3A%22http%3A%2F%2Flocalhost%3A3334%2Fv1%2Fverifiable-credentials%2F7a07aa7c-c961-4c74-a714-a6188cadd8f6.json%22%2C%22key%22%3A%222e2a9af227350b73733988ccb93581a5449cafa64394c13ffc7142b3b0b280b6%22%2C%22hash%22%3A%22a813f8aa1cdb5f391ec227d5a1f76a2764df7112bb26cd035b547a30ade03c8e%22%7D%7D', + headers: {}, + method: 'GET', + }, + QrLinkUnencrypted: { + url: 'http://localhost:3003/verify/?q=%7B%22payload%22%3A%7B%22uri%22%3A%22http%3A%2F%2Flocalhost%3A3334%2Fv1%2Fverifiable-credentials%2F61bdef77-0bcf-47d8-a6e6-00074e530e31.json%22%2C%20%22hash%22%3A%229db052ff7b36aa3be34c4a92fb9444314b786380815984d7aba81af97123ce16%22%7D%7D', headers: {}, method: 'GET', }, diff --git a/packages/vc-test-suite/jest.config.js b/packages/vc-test-suite/jest.config.js index 396b375f..c68b31e4 100644 --- a/packages/vc-test-suite/jest.config.js +++ b/packages/vc-test-suite/jest.config.js @@ -1,7 +1,7 @@ import base from '../../jest.config.base.js'; const jestConfig = { ...base, - collectCoverageFrom: ['/tests/**/*.{ts,tsx}', '!**/*.d.ts'], + collectCoverageFrom: ['/tests/**/*.{ts,tsx}', '!**/*.d.ts', '!/tests/**/*.test.{ts,tsx}'], preset: 'ts-jest', testEnvironment: 'node', roots: [''], diff --git a/packages/vc-test-suite/tests/QrlinkEncrypted/helper.ts b/packages/vc-test-suite/tests/QrLinkVerification/helper.ts similarity index 100% rename from packages/vc-test-suite/tests/QrlinkEncrypted/helper.ts rename to packages/vc-test-suite/tests/QrLinkVerification/helper.ts diff --git a/packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts b/packages/vc-test-suite/tests/QrLinkVerification/qrlink-verification.test.ts similarity index 51% rename from packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts rename to packages/vc-test-suite/tests/QrLinkVerification/qrlink-verification.test.ts index 17eeacd5..25662b8d 100644 --- a/packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts +++ b/packages/vc-test-suite/tests/QrLinkVerification/qrlink-verification.test.ts @@ -1,15 +1,15 @@ -import { decryptCredential } from '@mock-app/services'; import chai from 'chai'; -import config from '../../config'; + +import { computeHash, HashAlgorithm, decryptCredential } from '@mock-app/services'; + import { reportRow, setupMatrix } from '../../helpers'; import { request } from '../../httpService'; import { isURLEncoded, parseQRLink } from './helper'; -import { computeHash } from '@mock-app/services'; -import jwt from 'jsonwebtoken'; +import config from '../../config'; const expect = chai.expect; -describe('QR Link Verification', function () { +describe('QR Link Verification with encrypted data', function () { const { url: qrLink, method, headers } = config.testSuites.QrLinkEncrypted; const parsedLink = parseQRLink(qrLink); @@ -52,14 +52,12 @@ describe('QR Link Verification', function () { method, headers, }); - const stringifyVC = decryptCredential({ ...data, key: parsedLink.q.payload.key, }); - const vc = JSON.parse(stringifyVC); - const credentialHash = computeHash(vc); + const credentialHash = computeHash(vc, HashAlgorithm.SHA256); expect(parsedLink.q.payload.hash).to.equal(credentialHash); }); @@ -73,19 +71,68 @@ describe('QR Link Verification', function () { method, headers, }); - - const stringifyVC = decryptCredential({ + const credential = decryptCredential({ ...data, key: parsedLink.q.payload.key, }); - const vc = JSON.parse(stringifyVC); + credential.should.not.be.null; + credential.should.not.be.undefined; + credential.should.not.be.empty; + }); +}); + +describe('QR Link Verification with unencrypted data', function () { + const { url: qrLink, method, headers } = config.testSuites.QrLinkUnencrypted; + const parsedLink = parseQRLink(qrLink); - // Handle both JWT and regular JSON VC formats - const vcData = vc.id?.startsWith('data:application/vc-ld+jwt,') ? jwt.decode(vc.id.split(',')[1]) : vc; + setupMatrix.call(this, [config.implementationName], 'Implementer'); + + reportRow('Fails if decryption key exists', config.implementationName, function () { + expect(parsedLink.q.payload.key).to.be.undefined; + }); - expect(vcData).to.be.an('object'); - vcData.should.have.property('issuer'); - vcData.should.have.property('credentialSubject'); + reportRow('QR link MUST be URL encoded', config.implementationName, function () { + const data = isURLEncoded(qrLink); + data.should.be.true; + }); + + reportRow('Verification page link MUST exist and be a string', config.implementationName, function () { + expect(parsedLink.verify_app_address).to.be.a('string'); + }); + + reportRow('Payload MUST exist and be an object', config.implementationName, function () { + expect(parsedLink.q.payload).to.be.an('object'); + }); + + reportRow('URI MUST exist and be a string', config.implementationName, function () { + expect(parsedLink.q.payload.uri).to.be.a('string'); + }); + + reportRow('URI MUST be fetched', config.implementationName, async function () { + const { data } = await request({ + url: parsedLink.q.payload.uri, + method, + headers, + }); + + data.should.not.be.null; + data.should.not.be.undefined; + data.should.not.be.empty; + }); + + reportRow('Hash MUST exist and be a string', config.implementationName, function () { + expect(parsedLink.q.payload.hash).to.be.a('string'); + }); + + reportRow('Hash MUST match the credential hash', config.implementationName, async function () { + const { data } = await request({ + url: parsedLink.q.payload.uri, + method: 'GET', + headers: {}, + }); + + const credentialHash = computeHash(data, HashAlgorithm.SHA256); + expect(parsedLink.q.payload.hash).to.equal(credentialHash); }); });