-
Notifications
You must be signed in to change notification settings - Fork 124
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
11 changed files
with
627 additions
and
53 deletions.
There are no files selected for viewing
170 changes: 170 additions & 0 deletions
170
test/unit/http/WebHookSubscription2021UnsubscribeHttpHandler.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' }}}); | ||
}); | ||
}); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
23
test/unit/http/well-known/AggregateWellKnownBuilder.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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' }); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
49
test/unit/identity/configuration/BasicJwksKeyGenerator.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); | ||
}); |
Oops, something went wrong.