add FakeRedis testing utility
This commit is contained in:
parent
373456fa17
commit
1d5bc6928f
1 changed files with 145 additions and 0 deletions
145
packages/backend/test/misc/FakeRedis.ts
Normal file
145
packages/backend/test/misc/FakeRedis.ts
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import * as Redis from 'ioredis';
|
||||
|
||||
export type RedisKey = Redis.RedisKey;
|
||||
export type RedisString = Buffer | string;
|
||||
export type RedisNumber = string | number;
|
||||
export type RedisValue = RedisKey | RedisString | RedisNumber;
|
||||
export type RedisCallback<T = unknown> = Redis.Callback<T>;
|
||||
|
||||
export type Ok = 'OK';
|
||||
export const ok = 'OK' as const;
|
||||
|
||||
export type FakeRedis = Redis.Redis;
|
||||
export interface FakeRedisConstructor {
|
||||
new (): FakeRedis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fake implementation of Redis that pretends to connect but throws on any operation.
|
||||
*/
|
||||
export const FakeRedis: FakeRedisConstructor = createFakeRedis();
|
||||
|
||||
function createFakeRedis(): FakeRedisConstructor {
|
||||
class FakeRedis implements Partial<Redis.Redis> {
|
||||
async connect(callback?: RedisCallback<void>): Promise<void> {
|
||||
// no-op
|
||||
callback?.(null);
|
||||
}
|
||||
|
||||
async hello(...callbacks: (undefined | string | number | Buffer | RedisCallback<unknown[]>)[]): Promise<unknown[]> {
|
||||
// no-op
|
||||
const callback = callbacks.find(c => typeof(c) === 'function');
|
||||
callback?.(null, []);
|
||||
return [];
|
||||
}
|
||||
|
||||
async auth(...callbacks: (undefined | string | Buffer | RedisCallback<Ok>)[]): Promise<Ok> {
|
||||
const callback = callbacks.find(c => typeof(c) === 'function');
|
||||
callback?.(null, ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
async quit(callback?: RedisCallback<Ok>) {
|
||||
// no-op
|
||||
callback?.(null, ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
async save(callback?: RedisCallback<Ok>) {
|
||||
// no-op
|
||||
callback?.(null, ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
async sync(callback?: RedisCallback<Ok>) {
|
||||
// no-op
|
||||
callback?.(null, ok);
|
||||
return ok;
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
// no-op
|
||||
}
|
||||
|
||||
end(): void {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
const fakeProto = FakeRedis.prototype as Partial<Redis.Redis>;
|
||||
const redisProto = Redis.Redis.prototype as Partial<Redis.Redis>;
|
||||
|
||||
// Override all methods and accessors from Redis
|
||||
for (const [key, property] of allProps(redisProto)) {
|
||||
// Skip anything already defined
|
||||
if (Reflect.has(fakeProto, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (property.get || property.set) {
|
||||
// Stub accessors
|
||||
Reflect.defineProperty(fakeProto, key, {
|
||||
...property,
|
||||
get: property.get ? stub(property.get.name || key) : undefined,
|
||||
set: property.set ? stub(property.set.name || key) : undefined,
|
||||
});
|
||||
} else if (property.value && typeof(property.value) === 'function') {
|
||||
// Stub methods
|
||||
Reflect.defineProperty(fakeProto, key, {
|
||||
...property,
|
||||
value: stub(property.value.name || key),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Fixup protoype
|
||||
Reflect.setPrototypeOf(fakeProto, redisProto);
|
||||
|
||||
// test
|
||||
const test = new FakeRedis();
|
||||
if (!(test instanceof Redis.Redis)) {
|
||||
throw new Error('failed to extend');
|
||||
}
|
||||
|
||||
return FakeRedis as FakeRedisConstructor;
|
||||
}
|
||||
|
||||
function *allProps(obj: object | null): Generator<[PropertyKey, PropertyDescriptor]> {
|
||||
while (obj != null) {
|
||||
for (const key of Reflect.ownKeys(obj)) {
|
||||
const prop = Reflect.getOwnPropertyDescriptor(obj, key);
|
||||
if (prop) {
|
||||
yield [key, prop];
|
||||
}
|
||||
}
|
||||
|
||||
obj = Object.getPrototypeOf(obj);
|
||||
}
|
||||
}
|
||||
|
||||
function stub(name: PropertyKey) {
|
||||
if (typeof(name) === 'symbol') {
|
||||
name = `[symbol.${name.description || '<anonymous>'}]`;
|
||||
} else if (typeof(name) === 'number') {
|
||||
name = String(name);
|
||||
}
|
||||
|
||||
const stub = () => {
|
||||
throw new Error(`Not Implemented: MockRedis does not support ${name}`);
|
||||
};
|
||||
|
||||
// Make the stub match the original name
|
||||
Object.defineProperty(stub, 'name', {
|
||||
writable: false,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
value: name,
|
||||
});
|
||||
|
||||
return stub;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue