Skip to content

Commit

Permalink
fix and add unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
TamSzaGot committed Jan 28, 2022
1 parent ea0d608 commit d01c61f
Show file tree
Hide file tree
Showing 11 changed files with 627 additions and 53 deletions.
170 changes: 170 additions & 0 deletions test/unit/http/WebHookSubscription2021UnsubscribeHttpHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { createResponse } from 'node-mocks-http';
import type { CredentialSet } from '../../../src/authentication/Credentials';
import { CredentialGroup } from '../../../src/authentication/Credentials';
import type { CredentialsExtractor } from '../../../src/authentication/CredentialsExtractor';
import { RepresentationMetadata } from '../../../src/http/representation/RepresentationMetadata';
import { WebHookSubscription2021UnsubscribeHttpHandler }
from '../../../src/http/WebHookSubscription2021UnsubscribeHttpHandler';
import type { HttpRequest } from '../../../src/server/HttpRequest';
import type { HttpResponse } from '../../../src/server/HttpResponse';
import { BadRequestHttpError } from '../../../src/util/errors/BadRequestHttpError';
import { guardedStreamFrom } from '../../../src/util/StreamUtil';

describe('A WebHookSubscription2021UnsubscribeHttpHandler', (): void => {
const credentialsExtractor: CredentialsExtractor = {
canHandle: jest.fn(),
handle: jest.fn(),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handleSafe: jest.fn(async(request: HttpRequest): Promise<CredentialSet> => ({ [CredentialGroup.agent]: { webId: 'http://alice.example/card#me' }})),
};
const storageMap = new Map();
const notificationStorage = {
get: jest.fn((id: string): any => storageMap.get(id)),
set: jest.fn((id: string, value: any): any => storageMap.set(id, value)),
} as any;
it('throws error if no credential group agent present in request.', async(): Promise<void> => {
const noCredentialsExtractor: CredentialsExtractor = {
canHandle: jest.fn(),
handle: jest.fn(),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handleSafe: jest.fn(async(request: HttpRequest): Promise<CredentialSet> =>
({ [CredentialGroup.public]: undefined })),
};
const webHookSubscription2021UnsubscribeHttpHandler = new WebHookSubscription2021UnsubscribeHttpHandler({
baseUrl: 'http://server/',
credentialsExtractor: noCredentialsExtractor,
notificationStorage,
});
const metadata = new RepresentationMetadata();
const data = guardedStreamFrom([ 'data' ]);
const request = guardedStreamFrom([ 'test' ]) as HttpRequest;
const response = createResponse() as HttpResponse;
const promise = webHookSubscription2021UnsubscribeHttpHandler.handle({
operation: {
method: '',
target: { path: '' },
preferences: {},
body: { metadata, data, binary: false, isEmpty: true },
},
request,
response,
});
await expect(promise).rejects.toThrow(new BadRequestHttpError('No WebId present in request'));
});
it('throws error if no webId present in request.', async(): Promise<void> => {
const noCredentialsExtractor: CredentialsExtractor = {
canHandle: jest.fn(),
handle: jest.fn(),
// eslint-disable-next-line @typescript-eslint/no-unused-vars
handleSafe: jest.fn(async(request: HttpRequest): Promise<CredentialSet> =>
({ [CredentialGroup.agent]: { webId: undefined }})),
};
const webHookSubscription2021UnsubscribeHttpHandler = new WebHookSubscription2021UnsubscribeHttpHandler({
baseUrl: 'http://server/',
credentialsExtractor: noCredentialsExtractor,
notificationStorage,
});
const metadata = new RepresentationMetadata();
const data = guardedStreamFrom([ 'data' ]);
const request = guardedStreamFrom([ 'test' ]) as HttpRequest;
const response = createResponse() as HttpResponse;
const promise = webHookSubscription2021UnsubscribeHttpHandler.handle({
operation: {
method: '',
target: { path: '' },
preferences: {},
body: { metadata, data, binary: false, isEmpty: true },
},
request,
response,
});
await expect(promise).rejects.toThrow(new BadRequestHttpError('No WebId present in request'));
});
it('throws error if no url present in request.', async(): Promise<void> => {
const webHookSubscription2021UnsubscribeHttpHandler = new WebHookSubscription2021UnsubscribeHttpHandler({
baseUrl: 'http://server/',
credentialsExtractor,
notificationStorage,
});
const metadata = new RepresentationMetadata();
const data = guardedStreamFrom([ 'data' ]);
const request = guardedStreamFrom([ 'test' ]) as HttpRequest;
const response = createResponse() as HttpResponse;
const promise = webHookSubscription2021UnsubscribeHttpHandler.handle({
operation: {
method: '',
target: { path: '' },
preferences: {},
body: { metadata, data, binary: false, isEmpty: true },
},
request,
response,
});
await expect(promise).rejects.toThrow(new BadRequestHttpError('No url present in request'));
});
it('throws error if there is no matching subscription.', async(): Promise<void> => {
const webHookSubscription2021UnsubscribeHttpHandler = new WebHookSubscription2021UnsubscribeHttpHandler({
baseUrl: 'http://server/',
credentialsExtractor,
notificationStorage,
});
const metadata = new RepresentationMetadata();
const data = guardedStreamFrom([ 'data' ]);
const request = guardedStreamFrom([ 'test' ]) as HttpRequest;
request.method = 'DELETE';
request.url = 'BASEURL/webhook/http%3A%2F%2Flocalhost%3A9999%2Fresource~~~80d63ab0-afd0-464a-bc10-252b6d6fde0e';
const response = createResponse() as HttpResponse;
const promise = webHookSubscription2021UnsubscribeHttpHandler.handle({
operation: {
method: '',
target: { path: '' },
preferences: {},
body: { metadata, data, binary: false, isEmpty: true },
},
request,
response,
});
await expect(promise).rejects.toThrow(new BadRequestHttpError('Subscription does not exist'));
});
it('delete subscription if present.', async(): Promise<void> => {
storageMap.set('http://localhost:9999/resource', {
subscriptions: {
'http://alice.example/card#me': {
type: 'WebHookSubscription2021',
target: '',
id: 'http%3A%2F%2Flocalhost%3A9999%2Fresource~~~80d63ab0-afd0-464a-bc10-252b6d6fde0e',
},
'http://bob.example/card#me': {
type: 'WebHookSubscription2021',
target: '',
id: 'http%3A%2F%2Flocalhost%3A9999%2Fresource~~~00000000-afd0-464a-bc10-252b6d6fde0e',
},
},
});
const webHookSubscription2021UnsubscribeHttpHandler = new WebHookSubscription2021UnsubscribeHttpHandler({
baseUrl: 'http://server/',
credentialsExtractor,
notificationStorage,
});
const metadata = new RepresentationMetadata();
const data = guardedStreamFrom([ 'data' ]);
const request = guardedStreamFrom([ 'test' ]) as HttpRequest;
request.method = 'DELETE';
request.url = 'BASEURL/webhook/http%3A%2F%2Flocalhost%3A9999%2Fresource~~~80d63ab0-afd0-464a-bc10-252b6d6fde0e';
const response = createResponse() as HttpResponse;
const promise = webHookSubscription2021UnsubscribeHttpHandler.handle({
operation: {
method: '',
target: { path: '' },
preferences: {},
body: { metadata, data, binary: false, isEmpty: true },
},
request,
response,
});
await expect(promise).resolves.not.toThrow();
const subscription = storageMap.get('http://localhost:9999/resource');
expect(subscription).toEqual({ subscriptions: { 'http://bob.example/card#me': { id: 'http%3A%2F%2Flocalhost%3A9999%2Fresource~~~00000000-afd0-464a-bc10-252b6d6fde0e', target: '', type: 'WebHookSubscription2021' }}});
});
});

103 changes: 55 additions & 48 deletions test/unit/http/client/BaseHttpClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,65 @@
/* eslint-disable func-style */
import type { IncomingHttpHeaders, IncomingMessage, Server } from 'http';
import { URL } from 'url';
import { BaseHttpClient } from '../../../../src/http/client/BaseHttpClient';
import { BaseHttpServerFactory } from '../../../../src/server/BaseHttpServerFactory';
import { HttpHandler } from '../../../../src/server/HttpHandler';
import type { HttpHandlerInput } from '../../../../src/server/HttpHandler';

class MockHttpHandler extends HttpHandler {
public headers?: IncomingHttpHeaders;
public data?: any;
public async handle(input: HttpHandlerInput): Promise<void> {
const { request, response } = input;
const { headers, readableLength } = request;
const data = request.read(readableLength);

if (headers) {
this.headers = headers;
this.data = data;
response.statusCode = 200;
response.write('DUMMY');
response.end();
let errorListener: any;
let responseListener: any;
const on = jest.fn(
(event: string, listener: any): any => {
if (event === 'error') {
errorListener = listener;
} else if (event === 'response') {
responseListener = listener;
}
}
}
},
);
const write = jest.fn();
const end = jest.fn();
const request = {
on,
write,
end,
};
jest.mock('http', (): any => ({
request: (): any => request,
}));
jest.mock('https', (): any => ({
request: (): any => request,
}));

describe('A base http client', (): void => {
const httpHandler = new MockHttpHandler();
const port = 9898;
const url = `http://localhost:${port}`;
let server: Server;
beforeAll(async(): Promise<void> => {
server = new BaseHttpServerFactory(httpHandler, { https: false }).startServer(port);
it('should throw for unsupported protocols.', async(): Promise<void> => {
const client = new BaseHttpClient();
const response = client.call('file://path/foo/bar', { method: 'POST', headers: { accept: 'text/plain' }}, 'DATA');
await expect(response).rejects.toThrow(new Error(`Protocol file: not supported.`));
});
afterAll((): void => {
try {
server.close();
} catch {
// Ignored
}
it('should throw for unsupported protocols when given as URL.', async(): Promise<void> => {
const client = new BaseHttpClient();
const response = client.call(new URL('file://path/foo/bar'), { method: 'POST', headers: { accept: 'text/plain' }}, 'DATA');
await expect(response).rejects.toThrow(new Error(`Protocol file: not supported.`));
});
it('can errors on bad requests.', async(): Promise<void> => {
const client = new BaseHttpClient();
end.mockImplementationOnce((): any => {
errorListener({ message: 'error' });
return { statusCode: 400 };
});
const promise = client.call('http://server/foo/bar', { method: 'POST', headers: { accept: 'text/plain' }}, 'DATA');
await expect(promise).rejects.toThrow(new Error(`Fetch error: error`));
});
it('can send http requests.', async(): Promise<void> => {
const client = new BaseHttpClient();
end.mockImplementationOnce((): any => {
responseListener({ statusCode: 200 });
});
const promise = client.call('http://server/foo/bar', { method: 'POST', headers: { accept: 'text/plain' }}, 'DATA');
await expect(promise).resolves.toStrictEqual({ statusCode: 200 });
});
it('shoud send headers to the server.', async(): Promise<void> => {
it('can send https requests.', async(): Promise<void> => {
const client = new BaseHttpClient();
const callback: (res: IncomingMessage) => void = (res: IncomingMessage): void => {
expect(res.statusCode).toEqual(200);
};
const seconds: (duration: number) => Promise<void> = async function(duration: number): Promise<void> {
return new Promise<void>((resolve): void => {
setTimeout((): void => resolve(), duration * 1000);
});
};
client.call(url, { method: 'POST', headers: { accept: 'text/plain' }}, 'DATA', callback);
await seconds(1);
expect(httpHandler.headers).toEqual(
{ accept: 'text/plain', connection: 'close', host: `localhost:${port}`, 'transfer-encoding': 'chunked' },
);
expect((httpHandler.data as Buffer).toString()).toEqual(Buffer.from('DATA').toString());
end.mockImplementationOnce((): any => {
responseListener({ statusCode: 200 });
});
const promise = client.call('https://server/foo/bar', { method: 'POST', headers: { accept: 'text/plain' }}, 'DATA');
await expect(promise).resolves.toStrictEqual({ statusCode: 200 });
});
});
23 changes: 23 additions & 0 deletions test/unit/http/well-known/AggregateWellKnownBuilder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { AggregateWellKnownBuilder } from '../../../../src/http/well-known/AggregateWellKnownBuilder';
import type { WellKnownBuilder } from '../../../../src/http/well-known/WellKnownBuilder';

describe('An AggregateWellKnownBuilder', (): void => {
it('returns empty record if no builders are provided.', async(): Promise<void> => {
const wellKnownBuilders: WellKnownBuilder[] = [];
const aggregateWellKnownBuilder = new AggregateWellKnownBuilder(wellKnownBuilders);
const promise = aggregateWellKnownBuilder.getWellKnownSegment();
await expect(promise).resolves.toStrictEqual({});
});
it('returns the aggregated segments by using the provided builders.', async(): Promise<void> => {
const builder1: WellKnownBuilder = {
getWellKnownSegment: jest.fn((): Promise<Record<string, any>> => Promise.resolve({ builder1: 'segment1' })),
};
const builder2: WellKnownBuilder = {
getWellKnownSegment: jest.fn((): Promise<Record<string, any>> => Promise.resolve({ builder2: 'segment2' })),
};
const wellKnownBuilders: WellKnownBuilder[] = [ builder1, builder2 ];
const aggregateWellKnownBuilder = new AggregateWellKnownBuilder(wellKnownBuilders);
const promise = aggregateWellKnownBuilder.getWellKnownSegment();
await expect(promise).resolves.toStrictEqual({ builder1: 'segment1', builder2: 'segment2' });
});
});
32 changes: 32 additions & 0 deletions test/unit/http/well-known/WellKnownHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import type { ServerResponse } from 'http';
import type { WellKnownBuilder } from '../../../../src/http/well-known/WellKnownBuilder';
import { WellKnownHandler } from '../../../../src/http/well-known/WellKnownHandler';
import type { HttpRequest } from '../../../../src/server/HttpRequest';

describe('A WellKnownHandler', (): void => {
it('should write expected data to the response.', async(): Promise<void> => {
const builder: WellKnownBuilder = {
getWellKnownSegment: jest.fn((): Promise<Record<string, any>> => Promise.resolve({ builder: 'segment' })),
};
const wellKnownHandler = new WellKnownHandler(builder);
const request = {
} as unknown as HttpRequest;

const response = {
setHeader: jest.fn((): any => {}),
write: jest.fn((): any => {}),
end: jest.fn((): any => {}),
} as unknown as ServerResponse;

const setHeaderSpy = jest.spyOn(response, 'setHeader');

const promise = wellKnownHandler.handle({ request, response });
await expect(promise).resolves.not.toThrow();
expect(response.setHeader).toHaveBeenCalledTimes(1);
expect(setHeaderSpy.mock.calls).toEqual([[ 'Content-Type', 'application/ld+json' ]]);
expect(response.write).toHaveBeenCalledTimes(1);
expect(response.write).toHaveBeenCalledWith('{"builder":"segment"}');
expect(response.end).toHaveBeenCalledTimes(1);
});
});
49 changes: 49 additions & 0 deletions test/unit/identity/configuration/BasicJwksKeyGenerator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { BasicJwksKeyGenerator } from '../../../../src/identity/configuration/BasicJwksKeyGenerator';
import type { JwksKeyGenerator } from '../../../../src/identity/configuration/JwksKeyGenerator';
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
import { MemoryMapStorage } from '../../../../src/storage/keyvalue/MemoryMapStorage';

jest.mock('jose', (): any => ({
generateKeyPair: jest.fn(async(alg: string): Promise<any> => Promise.resolve(
{ privateKey: 'PRIVATE', publicKey: 'PUBLIC' },
)),
exportJWK: jest.fn(async(key: any): Promise<any> => Promise.resolve({ key })),
}));

describe('A BasicJwksKeyGenerator', (): void => {
let storage: KeyValueStorage<string, unknown>;
let basicJwksKeyGenerator: JwksKeyGenerator;
beforeEach(async(): Promise<void> => {
storage = new MemoryMapStorage();
basicJwksKeyGenerator = new BasicJwksKeyGenerator({ storage });
jest.clearAllMocks();
jest.spyOn(storage, 'set');
});
it('generates keys if not yet in storage and return expected private key.', async(): Promise<void> => {
const promise = basicJwksKeyGenerator.getPrivateJwks('test');
await expect(promise).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PRIVATE' }]});
await expect(storage.get(`test:private`)).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PRIVATE' }]});
await expect(storage.get(`test:public`)).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PUBLIC' }]});
expect(storage.set).toHaveBeenCalledTimes(2);
});
it('generates keys if not yet in storage and return expected public key.', async(): Promise<void> => {
const promise = basicJwksKeyGenerator.getPublicJwks('test');
await expect(promise).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PUBLIC' }]});
await expect(storage.get(`test:private`)).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PRIVATE' }]});
await expect(storage.get(`test:public`)).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PUBLIC' }]});
expect(storage.set).toHaveBeenCalledTimes(2);
});
it('get private key from storage if already generated.', async(): Promise<void> => {
await storage.set(`test:private`, { keys: [{ alg: 'RS256', key: 'PRIVATE' }]});
const promise = basicJwksKeyGenerator.getPrivateJwks('test');
await expect(promise).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PRIVATE' }]});
expect(storage.set).toHaveBeenCalledTimes(1);
});
it('get public key from storage if already generated.', async(): Promise<void> => {
await storage.set(`test:public`, { keys: [{ alg: 'RS256', key: 'PUBLIC' }]});
const promise = basicJwksKeyGenerator.getPublicJwks('test');
await expect(promise).resolves.toStrictEqual({ keys: [{ alg: 'RS256', key: 'PUBLIC' }]});
expect(storage.set).toHaveBeenCalledTimes(1);
});
});
Loading

0 comments on commit d01c61f

Please sign in to comment.