Skip to content

Commit

Permalink
chore: pass headers to request (#130)
Browse files Browse the repository at this point in the history
* chore: pass headers to request

* chore: update tests

* refactor: adjust import lodash for ESM

Signed-off-by: Nam Hoang <[email protected]>

---------

Signed-off-by: Nam Hoang <[email protected]>
Co-authored-by: Nam Hoang <[email protected]>
  • Loading branch information
ashleythedeveloper and namhoang1604 authored Oct 31, 2024
1 parent 3093f5e commit 93f2076
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 <token>`.

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
Expand All @@ -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.
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.
Original file line number Diff line number Diff line change
Expand Up @@ -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
- 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.
Original file line number Diff line number Diff line change
Expand Up @@ -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',
},
},
Expand All @@ -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'.
4 changes: 2 additions & 2 deletions packages/services/src/vckit.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down Expand Up @@ -208,7 +208,7 @@ export const decodeEnvelopedVC = (vc: VerifiableCredential): UnsignedCredential
* @param headers
*/
const _validateVckitHeaders = (headers: Record<string, string>) => {
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');
}
};
52 changes: 52 additions & 0 deletions packages/vc-test-suite/__tests__/httpService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { request } from '../httpService';
import axios from 'axios';

jest.mock('axios');

const mockedAxios = axios as jest.Mocked<typeof axios>;

describe('httpService', () => {
beforeEach(() => {
jest.clearAllMocks();

const mockRequest = jest.fn().mockResolvedValue({ data: 'mocked response' });

mockedAxios.create.mockReturnValue({
request: mockRequest,
} as any);
});

it('should accept a plain object for headers', async () => {
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 () => {
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.',
);
});

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);
});
});
2 changes: 1 addition & 1 deletion packages/vc-test-suite/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default {
},
RenderTemplate2024: {
url: 'http://localhost:3332/agent/renderCredential',
headers: {},
headers: { Authorization: 'Bearer test123' },
method: 'POST',
},
Storage: {
Expand Down
12 changes: 11 additions & 1 deletion packages/vc-test-suite/httpService.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import axios, { AxiosRequestConfig } from 'axios';
import _ from 'lodash';

const defaultHeaders = {
'Content-Type': 'application/json',
};

export const request = async (params: AxiosRequestConfig) => {
if (params.headers) {
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.');
}
}

const instance = axios.create({
headers: defaultHeaders,
headers: {
...defaultHeaders,
...params.headers,
},
});

const response = await instance.request(params);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
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';

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,
Expand All @@ -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({
Expand Down
Loading

0 comments on commit 93f2076

Please sign in to comment.