Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add test for qr link verification unencrypted data #156

Merged
merged 8 commits into from
Dec 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
};
```
Expand Down
9 changes: 8 additions & 1 deletion packages/untp-test-suite/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
'<rootDir>/src/**/*.ts',
],
coverageReporters: ['text', 'lcov', 'json', 'json-summary'],
coverageProvider: 'v8',
coverageDirectory: './coverage',
Expand Down
11 changes: 10 additions & 1 deletion packages/vc-test-suite/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
60 changes: 60 additions & 0 deletions packages/vc-test-suite/__tests__/QrLinkVerification/helper.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
9 changes: 7 additions & 2 deletions packages/vc-test-suite/config.ts
Original file line number Diff line number Diff line change
@@ -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',
},
Expand Down
2 changes: 1 addition & 1 deletion packages/vc-test-suite/jest.config.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base from '../../jest.config.base.js';
const jestConfig = {
...base,
collectCoverageFrom: ['<rootDir>/tests/**/*.{ts,tsx}', '!**/*.d.ts'],
collectCoverageFrom: ['<rootDir>/tests/**/*.{ts,tsx}', '!**/*.d.ts', '!<rootDir>/tests/**/*.test.{ts,tsx}'],
preset: 'ts-jest',
testEnvironment: 'node',
roots: ['<rootDir>'],
Expand Down
Original file line number Diff line number Diff line change
@@ -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);

Expand Down Expand Up @@ -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);
});

Expand All @@ -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);
});
});
Loading