feat: Create ConditionalHandler

This handler rejects all inputs once a certain condition is met
This commit is contained in:
Joachim Van Herwegen 2021-09-15 16:47:39 +02:00
parent fb0b50c997
commit facf691e86
3 changed files with 126 additions and 0 deletions

View File

@ -307,6 +307,7 @@ export * from './util/errors/UnsupportedMediaTypeHttpError';
// Util/Handlers
export * from './util/handlers/AsyncHandler';
export * from './util/handlers/BooleanHandler';
export * from './util/handlers/ConditionalHandler';
export * from './util/handlers/ParallelHandler';
export * from './util/handlers/SequenceHandler';
export * from './util/handlers/UnsupportedAsyncHandler';

View File

@ -0,0 +1,57 @@
import type { KeyValueStorage } from '../../storage/keyvalue/KeyValueStorage';
import { NotImplementedHttpError } from '../errors/NotImplementedHttpError';
import { AsyncHandler } from './AsyncHandler';
/**
* This handler will pass all requests to the wrapped handler,
* until a specific value has been set in the given storage.
* After that all input will be rejected.
* Once the value has been matched this behaviour will be cached,
* so changing the value again afterwards will not enable this handler again.
*/
export class ConditionalHandler<TIn, TOut> extends AsyncHandler<TIn, TOut> {
private readonly source: AsyncHandler<TIn, TOut>;
private readonly storage: KeyValueStorage<string, unknown>;
private readonly storageKey: string;
private readonly storageValue: unknown;
private finished: boolean;
public constructor(source: AsyncHandler<TIn, TOut>, storage: KeyValueStorage<string, unknown>, storageKey: string,
storageValue: unknown) {
super();
this.source = source;
this.storage = storage;
this.storageKey = storageKey;
this.storageValue = storageValue;
this.finished = false;
}
public async canHandle(input: TIn): Promise<void> {
await this.checkCondition();
await this.source.canHandle(input);
}
public async handleSafe(input: TIn): Promise<TOut> {
await this.checkCondition();
return this.source.handleSafe(input);
}
public async handle(input: TIn): Promise<TOut> {
return this.source.handle(input);
}
/**
* Checks if the condition has already been fulfilled.
*/
private async checkCondition(): Promise<void> {
if (!this.finished) {
this.finished = await this.storage.get(this.storageKey) === this.storageValue;
}
if (this.finished) {
throw new NotImplementedHttpError('The condition has been fulfilled.');
}
}
}

View File

@ -0,0 +1,68 @@
import type { KeyValueStorage } from '../../../../src/storage/keyvalue/KeyValueStorage';
import { NotImplementedHttpError } from '../../../../src/util/errors/NotImplementedHttpError';
import type { AsyncHandler } from '../../../../src/util/handlers/AsyncHandler';
import { ConditionalHandler } from '../../../../src/util/handlers/ConditionalHandler';
describe('A ConditionalHandler', (): void => {
const storageKey = 'completed';
const storageValue = true;
const input = 'input';
let storage: KeyValueStorage<string, unknown>;
let source: jest.Mocked<AsyncHandler<string, string>>;
let handler: ConditionalHandler<string, string>;
beforeEach(async(): Promise<void> => {
storage = new Map<string, boolean>() as any;
source = {
canHandle: jest.fn(),
handleSafe: jest.fn().mockResolvedValue('handledSafely'),
handle: jest.fn().mockResolvedValue('handled'),
};
handler = new ConditionalHandler(source, storage, storageKey, storageValue);
});
it('send canHandle input to the source.', async(): Promise<void> => {
await expect(handler.canHandle(input)).resolves.toBeUndefined();
expect(source.canHandle).toHaveBeenCalledTimes(1);
expect(source.canHandle).toHaveBeenLastCalledWith(input);
});
it('rejects all canHandle requests once the storage value matches.', async(): Promise<void> => {
await storage.set(storageKey, storageValue);
await expect(handler.canHandle(input)).rejects.toThrow(NotImplementedHttpError);
expect(source.canHandle).toHaveBeenCalledTimes(0);
});
it('caches the value of the storage.', async(): Promise<void> => {
await storage.set(storageKey, storageValue);
await expect(handler.canHandle(input)).rejects.toThrow(NotImplementedHttpError);
await storage.delete(storageKey);
await expect(handler.canHandle(input)).rejects.toThrow(NotImplementedHttpError);
});
it('redirects input to the source handleSafe call.', async(): Promise<void> => {
await expect(handler.handleSafe(input)).resolves.toBe('handledSafely');
expect(source.handleSafe).toHaveBeenCalledTimes(1);
expect(source.handleSafe).toHaveBeenLastCalledWith(input);
});
it('rejects all handleSafe requests once the storage value matches.', async(): Promise<void> => {
await storage.set(storageKey, storageValue);
await expect(handler.handleSafe(input)).rejects.toThrow(NotImplementedHttpError);
expect(source.handleSafe).toHaveBeenCalledTimes(0);
});
it('redirects input to the source handle call.', async(): Promise<void> => {
await expect(handler.handle(input)).resolves.toBe('handled');
expect(source.handle).toHaveBeenCalledTimes(1);
expect(source.handle).toHaveBeenLastCalledWith(input);
});
it('does not reject handle requests once the storage value matches.', async(): Promise<void> => {
await storage.set(storageKey, storageValue);
await expect(handler.handle(input)).resolves.toBe('handled');
expect(source.handle).toHaveBeenCalledTimes(1);
expect(source.handle).toHaveBeenLastCalledWith(input);
});
});