Initial Commit
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
import type { CacheSetOptions, CacheStats } from "@suaveplan/cache";
|
||||
import { AbstractCache } from "@suaveplan/cache";
|
||||
|
||||
/**
|
||||
* Real in-memory `AbstractCache<Uint8Array>` subclass used by the unit
|
||||
* tests. NOT a mock — it implements every abstract method against a
|
||||
* backing `Map`. The `failNext(op, err)` helper injects a deterministic
|
||||
* rejection for the next call to that op so the wrapper's
|
||||
* cache-failure-swallow paths are exercised against real throwing
|
||||
* behaviour.
|
||||
*
|
||||
* The class deliberately omits `keys()` by default; the
|
||||
* `KeysCapableFakeCache` subclass adds it. This lets the wrapper's
|
||||
* `clear()` feature-detection be tested against both shapes without a
|
||||
* mock.
|
||||
*/
|
||||
export class FakeCache extends AbstractCache<Uint8Array> {
|
||||
#data = new Map<string, Uint8Array>();
|
||||
public getCalls: string[] = [];
|
||||
public setCalls: { key: string; value: Uint8Array }[] = [];
|
||||
public deleteCalls: string[] = [];
|
||||
public clearCalls = 0;
|
||||
public hasCalls: string[] = [];
|
||||
|
||||
#failNext: Partial<
|
||||
Record<"get" | "set" | "delete" | "clear" | "has", Error>
|
||||
> = {};
|
||||
|
||||
public failNext(
|
||||
op: "get" | "set" | "delete" | "clear" | "has",
|
||||
err: Error
|
||||
): void {
|
||||
this.#failNext[op] = err;
|
||||
}
|
||||
|
||||
override async get(key: string): Promise<Uint8Array | undefined> {
|
||||
this.getCalls.push(key);
|
||||
const fail = this.#failNext.get;
|
||||
if (fail !== undefined) {
|
||||
this.#failNext.get = undefined;
|
||||
throw fail;
|
||||
}
|
||||
return this.#data.get(key);
|
||||
}
|
||||
|
||||
override async set(
|
||||
key: string,
|
||||
value: Uint8Array,
|
||||
_options?: CacheSetOptions
|
||||
): Promise<void> {
|
||||
this.setCalls.push({ key, value });
|
||||
const fail = this.#failNext.set;
|
||||
if (fail !== undefined) {
|
||||
this.#failNext.set = undefined;
|
||||
throw fail;
|
||||
}
|
||||
this.#data.set(key, value);
|
||||
}
|
||||
|
||||
override async delete(key: string): Promise<boolean> {
|
||||
this.deleteCalls.push(key);
|
||||
const fail = this.#failNext.delete;
|
||||
if (fail !== undefined) {
|
||||
this.#failNext.delete = undefined;
|
||||
throw fail;
|
||||
}
|
||||
return this.#data.delete(key);
|
||||
}
|
||||
|
||||
override async clear(): Promise<void> {
|
||||
this.clearCalls += 1;
|
||||
const fail = this.#failNext.clear;
|
||||
if (fail !== undefined) {
|
||||
this.#failNext.clear = undefined;
|
||||
throw fail;
|
||||
}
|
||||
this.#data.clear();
|
||||
}
|
||||
|
||||
override async has(key: string): Promise<boolean> {
|
||||
this.hasCalls.push(key);
|
||||
const fail = this.#failNext.has;
|
||||
if (fail !== undefined) {
|
||||
this.#failNext.has = undefined;
|
||||
throw fail;
|
||||
}
|
||||
return this.#data.has(key);
|
||||
}
|
||||
|
||||
override getStats(): CacheStats {
|
||||
return {
|
||||
hits: 0,
|
||||
misses: 0,
|
||||
size: this.#data.size,
|
||||
hitRate: 0,
|
||||
evictions: 0,
|
||||
};
|
||||
}
|
||||
|
||||
/** Test-only direct access. */
|
||||
public _peek(key: string): Uint8Array | undefined {
|
||||
return this.#data.get(key);
|
||||
}
|
||||
|
||||
/** Test-only direct seed. */
|
||||
public _seed(key: string, value: Uint8Array): void {
|
||||
this.#data.set(key, value);
|
||||
}
|
||||
|
||||
public get _data(): ReadonlyMap<string, Uint8Array> {
|
||||
return this.#data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `FakeCache` extension that exposes a synchronous `keys()` iterator
|
||||
* — the exact shape `CachedStorage.clear()` feature-detects.
|
||||
*/
|
||||
export class KeysCapableFakeCache extends FakeCache {
|
||||
public keysCalls = 0;
|
||||
|
||||
public *keys(): IterableIterator<string> {
|
||||
this.keysCalls += 1;
|
||||
for (const k of this._data.keys()) yield k;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* `FakeCache` extension exposing an async `keys()` iterator —
|
||||
* representative of remote cache backends that stream keys.
|
||||
*/
|
||||
export class AsyncKeysCapableFakeCache extends FakeCache {
|
||||
public keysCalls = 0;
|
||||
|
||||
public async *keys(): AsyncIterableIterator<string> {
|
||||
this.keysCalls += 1;
|
||||
for (const k of this._data.keys()) {
|
||||
yield k;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user