Initial Commit
This commit is contained in:
142
packages/persistence/cache/cache-redis/tests/fixtures/fake-redis.ts
vendored
Normal file
142
packages/persistence/cache/cache-redis/tests/fixtures/fake-redis.ts
vendored
Normal file
@@ -0,0 +1,142 @@
|
||||
import type {
|
||||
RedisLike,
|
||||
RedisScanResult,
|
||||
} from "../../src/redis-like/redis-like.js";
|
||||
|
||||
interface Entry {
|
||||
value: string;
|
||||
expiresAt: number;
|
||||
}
|
||||
|
||||
export interface FakeRedisCall {
|
||||
command: string;
|
||||
args: ReadonlyArray<unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal in-memory `RedisLike` implementation used by the cache-redis test
|
||||
* suite. Tracks every command in `calls` so assertions can verify the exact
|
||||
* wire `RedisCache` issued.
|
||||
*/
|
||||
export class FakeRedis implements RedisLike {
|
||||
public readonly calls: FakeRedisCall[] = [];
|
||||
|
||||
readonly #store = new Map<string, Entry>();
|
||||
#now: () => number;
|
||||
|
||||
public constructor(now: () => number = () => Date.now()) {
|
||||
this.#now = now;
|
||||
}
|
||||
|
||||
public setClock(now: () => number): void {
|
||||
this.#now = now;
|
||||
}
|
||||
|
||||
#record(command: string, args: ReadonlyArray<unknown>): void {
|
||||
this.calls.push({ command, args });
|
||||
}
|
||||
|
||||
public async set(
|
||||
key: string,
|
||||
value: string,
|
||||
options?: { EX?: number; PX?: number }
|
||||
): Promise<"OK"> {
|
||||
this.#record(
|
||||
"SET",
|
||||
options === undefined ? [key, value] : [key, value, options]
|
||||
);
|
||||
let expiresAt = Number.POSITIVE_INFINITY;
|
||||
if (options?.EX !== undefined) {
|
||||
expiresAt = this.#now() + options.EX * 1000;
|
||||
} else if (options?.PX !== undefined) {
|
||||
expiresAt = this.#now() + options.PX;
|
||||
}
|
||||
this.#store.set(key, { value, expiresAt });
|
||||
return "OK";
|
||||
}
|
||||
|
||||
public async get(key: string): Promise<string | null> {
|
||||
this.#record("GET", [key]);
|
||||
const entry = this.#store.get(key);
|
||||
if (entry === undefined) return null;
|
||||
if (entry.expiresAt <= this.#now()) {
|
||||
this.#store.delete(key);
|
||||
return null;
|
||||
}
|
||||
return entry.value;
|
||||
}
|
||||
|
||||
public async del(...keys: string[]): Promise<number> {
|
||||
this.#record("DEL", keys);
|
||||
let n = 0;
|
||||
for (const k of keys) {
|
||||
if (this.#store.delete(k)) n += 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public async exists(...keys: string[]): Promise<number> {
|
||||
this.#record("EXISTS", keys);
|
||||
let n = 0;
|
||||
for (const k of keys) {
|
||||
const e = this.#store.get(k);
|
||||
if (e === undefined) continue;
|
||||
if (e.expiresAt > this.#now()) n += 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
public async ttl(key: string): Promise<number> {
|
||||
this.#record("TTL", [key]);
|
||||
const entry = this.#store.get(key);
|
||||
if (entry === undefined) return -2;
|
||||
if (entry.expiresAt === Number.POSITIVE_INFINITY) return -1;
|
||||
const remaining = Math.ceil((entry.expiresAt - this.#now()) / 1000);
|
||||
return remaining > 0 ? remaining : -2;
|
||||
}
|
||||
|
||||
public async expire(key: string, seconds: number): Promise<number> {
|
||||
this.#record("EXPIRE", [key, seconds]);
|
||||
const entry = this.#store.get(key);
|
||||
if (entry === undefined) return 0;
|
||||
entry.expiresAt = this.#now() + seconds * 1000;
|
||||
return 1;
|
||||
}
|
||||
|
||||
public async scan(
|
||||
cursor: string,
|
||||
options?: { MATCH?: string; COUNT?: number }
|
||||
): Promise<RedisScanResult> {
|
||||
this.#record("SCAN", [cursor, options]);
|
||||
const match = options?.MATCH;
|
||||
const re =
|
||||
match === undefined
|
||||
? null
|
||||
: new RegExp(
|
||||
`^${match
|
||||
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
||||
.replace(/\*/g, ".*")}$`
|
||||
);
|
||||
const keys: string[] = [];
|
||||
for (const k of this.#store.keys()) {
|
||||
if (re === null || re.test(k)) keys.push(k);
|
||||
}
|
||||
return { cursor: "0", keys };
|
||||
}
|
||||
}
|
||||
|
||||
/** RedisLike that fails every command with the given error. */
|
||||
export function makeFailingRedis(error: Error): RedisLike {
|
||||
const fail = async (): Promise<never> => {
|
||||
throw error;
|
||||
};
|
||||
return {
|
||||
set: fail,
|
||||
get: fail,
|
||||
del: fail,
|
||||
exists: fail,
|
||||
ttl: fail,
|
||||
expire: fail,
|
||||
scan: fail,
|
||||
};
|
||||
}
|
||||
7
packages/persistence/cache/cache-redis/tests/setup/polyfills.ts
vendored
Normal file
7
packages/persistence/cache/cache-redis/tests/setup/polyfills.ts
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
import { webcrypto } from "node:crypto";
|
||||
|
||||
type WritableGlobal = { crypto?: Crypto };
|
||||
const g = globalThis as WritableGlobal;
|
||||
if (g.crypto === undefined) {
|
||||
g.crypto = webcrypto as unknown as Crypto;
|
||||
}
|
||||
Reference in New Issue
Block a user