From 09972bb519e97aab6cfa81514c527ee6281fc8b5 Mon Sep 17 00:00:00 2001 From: Ashley Harwood Date: Tue, 22 Oct 2024 22:40:17 +1100 Subject: [PATCH 1/3] chore: pass headers to request --- .../getting-started.md | 4 +- .../storage/testing.md | 14 +++- .../untp-extensions/rendering.md | 8 +- .../__tests__/httpService.test.ts | 32 ++++++++ packages/vc-test-suite/config.ts | 2 +- packages/vc-test-suite/httpService.ts | 9 ++- .../QrlinkEncrypted/qrlink-encrypted.test.ts | 28 +++---- .../render-template-2024.test.ts | 73 ++++++++++--------- 8 files changed, 117 insertions(+), 53 deletions(-) create mode 100644 packages/vc-test-suite/__tests__/httpService.test.ts diff --git a/documentation/docs/test-suites/technical-interoperability/getting-started.md b/documentation/docs/test-suites/technical-interoperability/getting-started.md index d68b2c31..cca8fc2a 100644 --- a/documentation/docs/test-suites/technical-interoperability/getting-started.md +++ b/documentation/docs/test-suites/technical-interoperability/getting-started.md @@ -21,6 +21,8 @@ The Getting Started section covers the following topics for technical interopera **Note**: The preset configuration in the config file (`packages/vc-test-suite/config.ts`) has already been preconfigured to use the services set up with Docker Compose. If you're using the default Docker setup, you may not need to modify these settings. + **Note**: In the config file, you can specify headers to be included in the request for each test suite within the Tier 1 test suite (e.g. `RenderTemplate2024`, `Storage`, etc.). Each test case will inherit these headers from the respective config. This is particularly useful if you need to include an authorization token, such as `Bearer `. + 3. **Usage**: Learn how to run the various components of the technical interoperability test suite and interpret the results. This section will guide you through: - Running the W3C V2 VCDM tests - Executing UNTP extension tests @@ -30,4 +32,4 @@ The Getting Started section covers the following topics for technical interopera By the end of this section, you will have a solid foundation for installing, configuring, and using the UNTP Technical Interoperability Test Suite. You'll be able to validate your UNTP implementation's technical components against the required standards. -If you're using the default Docker setup, most of the configuration has been done for you. However, if you need to customise any settings or are using a different setup, the configuration section of each component will guide you through the necessary steps. \ No newline at end of file +If you're using the default Docker setup, most of the configuration has been done for you. However, if you need to customise any settings or are using a different setup, the configuration section of each component will guide you through the necessary steps. diff --git a/documentation/docs/test-suites/technical-interoperability/storage/testing.md b/documentation/docs/test-suites/technical-interoperability/storage/testing.md index f0397a28..7a6fe976 100644 --- a/documentation/docs/test-suites/technical-interoperability/storage/testing.md +++ b/documentation/docs/test-suites/technical-interoperability/storage/testing.md @@ -43,4 +43,16 @@ To test your Storage implementation, follow these steps: 3. **View Test Results**: - Navigate to `packages/vc-test-suite/reports/index.html` - Open this file in a web browser - - Look for the "Storage Service" section to view your test results \ No newline at end of file + - Look for the "Storage Service" section to view your test results + +- `url`: This is the full URL for the unencrypted storage service endpoint of your storage service. + +- `encryptionUrl`: This is the full URL for the encryption service endpoint of your storage service. + +- `headers`: An object containing any additional HTTP headers required for the request to the storage service. You may need to add headers depending on your implementation. + +- `method`: The HTTP method used to request the verifiable credential from the storage service. In this case, it's set to 'POST'. + +- `additionalParams`: An object containing any additional parameters required for the request to the storage service. + +- `additionalPayload`: An object containing any additional payload required for the request to the storage service. diff --git a/documentation/docs/test-suites/technical-interoperability/untp-extensions/rendering.md b/documentation/docs/test-suites/technical-interoperability/untp-extensions/rendering.md index d6fdd05f..3b5ea429 100644 --- a/documentation/docs/test-suites/technical-interoperability/untp-extensions/rendering.md +++ b/documentation/docs/test-suites/technical-interoperability/untp-extensions/rendering.md @@ -87,7 +87,7 @@ To test your Rendering implementation, follow these steps: testSuites: { RenderTemplate2024: { url: 'http://localhost:3332/agent/renderCredential', - headers: {}, + headers: { Authorization: 'Bearer test123' }, method: 'POST', }, }, @@ -105,3 +105,9 @@ To test your Rendering implementation, follow these steps: - Navigate to `packages/vc-test-suite/reports/index.html` - Open this file in a web browser - Look for the "RenderTemplate2024" section to view your test results + +- `url`: This is the full URL for the credential renderer endpoint of your verifiable credential service. + +- `headers`: An object containing any additional HTTP headers required for the request to the verifiable credential service. You may need to add headers depending on your implementation. + +- `method`: The HTTP method used to request the rendered credential from the verifiable credential service. In this case, it's set to 'POST'. diff --git a/packages/vc-test-suite/__tests__/httpService.test.ts b/packages/vc-test-suite/__tests__/httpService.test.ts new file mode 100644 index 00000000..4e69ee42 --- /dev/null +++ b/packages/vc-test-suite/__tests__/httpService.test.ts @@ -0,0 +1,32 @@ +import { request } from '../httpService'; +import axios from 'axios'; + +jest.mock('axios'); + +const mockedAxios = axios as jest.Mocked; + +describe('httpService', () => { + beforeEach(() => { + mockedAxios.create.mockReturnValue({ + request: jest.fn().mockResolvedValue({ data: 'mocked response' }), + } as any); + }); + + it('should accept a plain object for headers', async () => { + const result = await request({ headers: { test: 'test' } }); + expect(result).toBeDefined(); + expect(result).toEqual({ data: 'mocked response' }); + }); + + it('should throw an error if headers is not a plain object', async () => { + await expect(request({ headers: [] as any })).rejects.toThrow( + 'Headers specified in the config must be a plain object with string values.', + ); + }); + + it('should throw an error if headers contain non-string values', async () => { + await expect(request({ headers: { test: 123 } as any })).rejects.toThrow( + 'Headers specified in the config must be a plain object with string values.', + ); + }); +}); diff --git a/packages/vc-test-suite/config.ts b/packages/vc-test-suite/config.ts index 4713fff7..7afd4551 100644 --- a/packages/vc-test-suite/config.ts +++ b/packages/vc-test-suite/config.ts @@ -8,7 +8,7 @@ export default { }, RenderTemplate2024: { url: 'http://localhost:3332/agent/renderCredential', - headers: {}, + headers: { Authorization: 'Bearer test123' }, method: 'POST', }, Storage: { diff --git a/packages/vc-test-suite/httpService.ts b/packages/vc-test-suite/httpService.ts index 05334e82..69b1c0d5 100644 --- a/packages/vc-test-suite/httpService.ts +++ b/packages/vc-test-suite/httpService.ts @@ -1,5 +1,5 @@ import axios, { AxiosRequestConfig } from 'axios'; - +import { isPlainObject, every, isString } from 'lodash'; const defaultHeaders = { 'Content-Type': 'application/json', }; @@ -9,6 +9,13 @@ export const request = async (params: AxiosRequestConfig) => { headers: defaultHeaders, }); + // Check if user defined headers is an object and contains only string values + if (params.headers) { + if (!isPlainObject(params.headers) || !every(params.headers, isString)) { + throw new Error('Headers specified in the config must be a plain object with string values.'); + } + } + const response = await instance.request(params); return response; }; diff --git a/packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts b/packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts index 56992ece..4684d46f 100644 --- a/packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts +++ b/packages/vc-test-suite/tests/QrlinkEncrypted/qrlink-encrypted.test.ts @@ -1,6 +1,6 @@ import { decryptCredential } from '@mock-app/services'; import chai from 'chai'; -import * as config from '../../config'; +import config from '../../config'; import { reportRow, setupMatrix } from '../../helpers'; import { request } from '../../httpService'; import { isURLEncoded, parseQRLink } from './helper'; @@ -8,47 +8,48 @@ import { isURLEncoded, parseQRLink } from './helper'; const expect = chai.expect; describe('QR Link Verification', function () { - const qrLink = config.default.testSuites.QrLinkEncrypted.url; - const method = config.default.testSuites.QrLinkEncrypted.method; + const { url: qrLink, method, headers } = config.testSuites.QrLinkEncrypted; const parsedLink = parseQRLink(qrLink); - setupMatrix.call(this, [config.default.implementationName], 'Implementer'); + setupMatrix.call(this, [config.implementationName], 'Implementer'); - reportRow('QR link MUST be URL encoded', config.default.implementationName, function () { + 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.default.implementationName, function () { + 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.default.implementationName, function () { + 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.default.implementationName, function () { + reportRow('URI MUST exist and be a string', config.implementationName, function () { expect(parsedLink.q.payload.uri).to.be.a('string'); }); - reportRow('URI MUST be resolvable', config.default.implementationName, async function () { + reportRow('URI MUST be resolvable', config.implementationName, async function () { const { data } = await request({ url: parsedLink.q.payload.uri, method, + headers, }); data.should.be.an('object'); }); - reportRow('Hash MUST exist and be a string', config.default.implementationName, function () { + 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.default.implementationName, async function () { + reportRow('Hash MUST match the credential hash', config.implementationName, async function () { // TODO: Implement this test case with hash comparison // const res = await request({ // url: parsedLink.q.payload.uri, // method: 'GET', + // headers, // }); // const stringifyVC = decryptCredential({ // ...res.document, @@ -59,14 +60,15 @@ describe('QR Link Verification', function () { // expect(parsedLink.q.payload.hash).to.equal(credentialHash); }); - reportRow('Key exist and be a string', config.default.implementationName, function () { + reportRow('Key exist and be a string', config.implementationName, function () { expect(parsedLink.q.payload.key).to.be.a('string'); }); - reportRow('Key MUST decrypt the encrypted credential', config.default.implementationName, async function () { + reportRow('Key MUST decrypt the encrypted credential', config.implementationName, async function () { const { data } = await request({ url: parsedLink.q.payload.uri, method, + headers, }); const stringifyVC = decryptCredential({ diff --git a/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts b/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts index a0e63ec0..374eded0 100644 --- a/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts +++ b/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts @@ -1,21 +1,20 @@ /* eslint-disable @typescript-eslint/no-empty-function */ import assert from 'assert'; import chai from 'chai'; -import * as config from '../../config'; +import config from '../../config'; import { reportRow, setupMatrix } from '../../helpers'; import { request } from '../../httpService'; chai.should(); describe('RenderTemplate2024', function () { - const url = config.default.testSuites.RenderTemplate2024.url; - const method = config.default.testSuites.RenderTemplate2024.method; + const { url, method, headers } = config.testSuites.RenderTemplate2024; - setupMatrix.call(this, [config.default.implementationName], 'Implementer'); + setupMatrix.call(this, [config.implementationName], 'Implementer'); reportRow( 'should verify that each item in the documents array that is valid', - config.default.implementationName, + config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/valid-request-payload-ok.json'); @@ -25,6 +24,7 @@ describe('RenderTemplate2024', function () { method, url, data: input, + headers, }); data.should.eql({ @@ -42,7 +42,7 @@ describe('RenderTemplate2024', function () { reportRow( "should verify that the response fails when the request payload is missing the 'credential' property", - config.default.implementationName, + config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/missing-credential-fail.json'); @@ -53,6 +53,7 @@ describe('RenderTemplate2024', function () { method, url, data: input, + headers, }), ); }, @@ -60,7 +61,7 @@ describe('RenderTemplate2024', function () { reportRow( 'should verify that the response fails when the request payload is empty', - config.default.implementationName, + config.implementationName, async () => { // Send an empty payload to the service and expect it to fail. await assert.rejects( @@ -68,6 +69,7 @@ describe('RenderTemplate2024', function () { method, url, data: {}, + headers, }), ); }, @@ -75,7 +77,7 @@ describe('RenderTemplate2024', function () { reportRow( "should verify that the 'name' field, if present, is a non-empty string", - config.default.implementationName, + config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/name-field-empty-ok.json'); @@ -85,6 +87,7 @@ describe('RenderTemplate2024', function () { method, url, data: input, + headers, }); // Verify each item in the returned documents array. @@ -98,34 +101,31 @@ describe('RenderTemplate2024', function () { ); // TODO check grammar - reportRow( - "should successfully render when 'mediaQuery' is not provided", - config.default.implementationName, - async () => { - // Import the input data for the test from the specified JSON file. - const input = require('./input/no-mediaQuery-in-renderMethod-ok.json'); - - // Send the input data to the service to get the rendered document response. - const { data } = await request({ - method, - url, - data: input, - }); - - data.should.eql({ - documents: [ - { - type: 'RenderTemplate2024', - renderedTemplate: 'PHA+SmFuZSBEb2U8L3A+', - }, - ], - }); - }, - ); + reportRow("should successfully render when 'mediaQuery' is not provided", config.implementationName, async () => { + // Import the input data for the test from the specified JSON file. + const input = require('./input/no-mediaQuery-in-renderMethod-ok.json'); + + // Send the input data to the service to get the rendered document response. + const { data } = await request({ + method, + url, + data: input, + headers, + }); + + data.should.eql({ + documents: [ + { + type: 'RenderTemplate2024', + renderedTemplate: 'PHA+SmFuZSBEb2U8L3A+', + }, + ], + }); + }); reportRow( 'should successfully render when the request payload contains additional properties', - config.default.implementationName, + config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/additional-properties-ok.json'); @@ -135,6 +135,7 @@ describe('RenderTemplate2024', function () { method, url, data: input, + headers, }); // Verify each item in the returned documents array. @@ -153,7 +154,7 @@ describe('RenderTemplate2024', function () { reportRow( "should verify that the response fails when the request payload is missing '@context'", - config.default.implementationName, + config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/missing-@context-fail.json'); @@ -164,6 +165,7 @@ describe('RenderTemplate2024', function () { method, url, data: input, + headers, }), ); }, @@ -171,7 +173,7 @@ describe('RenderTemplate2024', function () { reportRow( "should verify that the response fails when the request payload is missing 'renderMethod'", - config.default.implementationName, + config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/missing-renderMethod-fail.json'); @@ -182,6 +184,7 @@ describe('RenderTemplate2024', function () { method, url, data: input, + headers, }), ); }, From e86b62391e6864371d422e4d07245997d33fadf2 Mon Sep 17 00:00:00 2001 From: Ashley Harwood Date: Tue, 29 Oct 2024 09:42:29 +1100 Subject: [PATCH 2/3] chore: update tests --- .../__tests__/httpService.test.ts | 26 ++++++++++++++++--- packages/vc-test-suite/httpService.ts | 13 ++++++---- .../render-template-2024.test.ts | 3 +-- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/packages/vc-test-suite/__tests__/httpService.test.ts b/packages/vc-test-suite/__tests__/httpService.test.ts index 4e69ee42..1bf75a96 100644 --- a/packages/vc-test-suite/__tests__/httpService.test.ts +++ b/packages/vc-test-suite/__tests__/httpService.test.ts @@ -7,15 +7,24 @@ const mockedAxios = axios as jest.Mocked; describe('httpService', () => { beforeEach(() => { + jest.clearAllMocks(); + + const mockRequest = jest.fn().mockResolvedValue({ data: 'mocked response' }); + mockedAxios.create.mockReturnValue({ - request: jest.fn().mockResolvedValue({ data: 'mocked response' }), + request: mockRequest, } as any); }); it('should accept a plain object for headers', async () => { - const result = await request({ headers: { test: 'test' } }); - expect(result).toBeDefined(); + const params = { headers: { test: 'test' } }; + const result = await request(params); + expect(result).toEqual({ data: 'mocked response' }); + + const mockAxiosInstance = mockedAxios.create.mock.results[0].value; + expect(mockAxiosInstance.request).toHaveBeenCalledTimes(1); + expect(mockAxiosInstance.request).toHaveBeenCalledWith(params); }); it('should throw an error if headers is not a plain object', async () => { @@ -29,4 +38,15 @@ describe('httpService', () => { 'Headers specified in the config must be a plain object with string values.', ); }); + + it('should work when no headers are provided in params', async () => { + const params = { url: 'test-url' }; + const result = await request(params); + + expect(result).toEqual({ data: 'mocked response' }); + + const mockAxiosInstance = mockedAxios.create.mock.results[0].value; + expect(mockAxiosInstance.request).toHaveBeenCalledTimes(1); + expect(mockAxiosInstance.request).toHaveBeenCalledWith(params); + }); }); diff --git a/packages/vc-test-suite/httpService.ts b/packages/vc-test-suite/httpService.ts index 69b1c0d5..2d4a8c52 100644 --- a/packages/vc-test-suite/httpService.ts +++ b/packages/vc-test-suite/httpService.ts @@ -1,21 +1,24 @@ import axios, { AxiosRequestConfig } from 'axios'; import { isPlainObject, every, isString } from 'lodash'; + const defaultHeaders = { 'Content-Type': 'application/json', }; export const request = async (params: AxiosRequestConfig) => { - const instance = axios.create({ - headers: defaultHeaders, - }); - - // Check if user defined headers is an object and contains only string values if (params.headers) { if (!isPlainObject(params.headers) || !every(params.headers, isString)) { throw new Error('Headers specified in the config must be a plain object with string values.'); } } + const instance = axios.create({ + headers: { + ...defaultHeaders, + ...params.headers, + }, + }); + const response = await instance.request(params); return response; }; diff --git a/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts b/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts index 374eded0..38535cf7 100644 --- a/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts +++ b/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts @@ -100,8 +100,7 @@ describe('RenderTemplate2024', function () { }, ); - // TODO check grammar - reportRow("should successfully render when 'mediaQuery' is not provided", config.implementationName, async () => { + reportRow('should render successfully when ‘mediaQuery’ is not provided', config.implementationName, async () => { // Import the input data for the test from the specified JSON file. const input = require('./input/no-mediaQuery-in-renderMethod-ok.json'); From f0e9ac66704ac380d3070ec9ada91a3c4dc653f9 Mon Sep 17 00:00:00 2001 From: Nam Hoang Date: Tue, 29 Oct 2024 17:29:25 +0700 Subject: [PATCH 3/3] refactor: adjust import lodash for ESM Signed-off-by: Nam Hoang --- packages/services/src/vckit.service.ts | 4 ++-- packages/vc-test-suite/httpService.ts | 4 ++-- .../tests/RenderTemplate2024/render-template-2024.test.ts | 2 ++ 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/services/src/vckit.service.ts b/packages/services/src/vckit.service.ts index 0c7f3509..d5b585f7 100644 --- a/packages/services/src/vckit.service.ts +++ b/packages/services/src/vckit.service.ts @@ -9,7 +9,7 @@ import { } from '@vckit/core-types'; import { decodeJwt } from 'jose'; import { privateAPI } from './utils/httpService.js'; -import { isPlainObject, every, isString } from 'lodash'; +import _ from 'lodash'; export const contextDefault = [ 'https://www.w3.org/ns/credentials/v2', @@ -208,7 +208,7 @@ export const decodeEnvelopedVC = (vc: VerifiableCredential): UnsignedCredential * @param headers */ const _validateVckitHeaders = (headers: Record) => { - if (!isPlainObject(headers) || !every(headers, isString)) { + if (!_.isPlainObject(headers) || !_.every(headers, (value) => _.isString(value))) { throw new Error('VcKit headers defined in app config must be a plain object with string values'); } }; diff --git a/packages/vc-test-suite/httpService.ts b/packages/vc-test-suite/httpService.ts index 2d4a8c52..8539dda5 100644 --- a/packages/vc-test-suite/httpService.ts +++ b/packages/vc-test-suite/httpService.ts @@ -1,5 +1,5 @@ import axios, { AxiosRequestConfig } from 'axios'; -import { isPlainObject, every, isString } from 'lodash'; +import _ from 'lodash'; const defaultHeaders = { 'Content-Type': 'application/json', @@ -7,7 +7,7 @@ const defaultHeaders = { export const request = async (params: AxiosRequestConfig) => { if (params.headers) { - if (!isPlainObject(params.headers) || !every(params.headers, isString)) { + if (!_.isPlainObject(params.headers) || !_.every(params.headers, (value) => _.isString(value))) { throw new Error('Headers specified in the config must be a plain object with string values.'); } } diff --git a/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts b/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts index 38535cf7..cca56b87 100644 --- a/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts +++ b/packages/vc-test-suite/tests/RenderTemplate2024/render-template-2024.test.ts @@ -4,6 +4,8 @@ import chai from 'chai'; import config from '../../config'; import { reportRow, setupMatrix } from '../../helpers'; import { request } from '../../httpService'; +import { createRequire } from 'module'; +const require = createRequire(import.meta.url); chai.should();