UNPKG

@daiso-tech/core

Version:

The library offers flexible, framework-agnostic solutions for modern web applications, built on adaptable components that integrate seamlessly with popular frameworks like Next Js.

321 lines 15.7 kB
/** * @module Cache */ import {} from "vitest"; import {} from "../../../cache/contracts/_module.js"; import {} from "../../../execution-context/contracts/_module.js"; import { NoOpExecutionContextAdapter } from "../../../execution-context/implementations/adapters/no-op-execution-context-adapter/_module.js"; import { ExecutionContext } from "../../../execution-context/implementations/derivables/_module.js"; import {} from "../../../time-span/contracts/_module.js"; import { TimeSpan } from "../../../time-span/implementations/_module.js"; import { delay } from "../../../utilities/_module.js"; /** * The `cacheAdapterTestSuite` function simplifies the process of testing your custom implementation of {@link ICacheAdapter | `ICacheAdapter`} with `vitest`. * * IMPORT_PATH: `"@daiso-tech/core/cache/test-utilities"` * @group TestUtilities * @example * ```ts * import { afterEach, beforeEach, describe, expect, test } from "vitest"; * import { Redis } from "ioredis"; * import { * RedisContainer, * type StartedRedisContainer, * } from "@testcontainers/redis"; * import { cacheAdapterTestSuite } from "@daiso-tech/core/cache/test-utilities"; * import { RedisCacheAdapter } from "@daiso-tech/core/cache/redis-cache-adapter"; * import { TimeSpan } from "@daiso-tech/core/time-span"; * import { SuperJsonSerdeAdapter } from "@daiso-tech/core/serde/super-json-serde-adapter"; * import { Serde } from "@daiso-tech/core/serde"; * * const timeout = TimeSpan.fromMinutes(2); * describe("class: RedisCacheAdapter", () => { * let client: Redis; * let startedContainer: StartedRedisContainer; * beforeEach(async () => { * startedContainer = await new RedisContainer("redis:7.4.2").start(); * client = new Redis(startedContainer.getConnectionUrl()); * }, timeout.toMilliseconds()); * afterEach(async () => { * await client.quit(); * await startedContainer.stop(); * }, timeout.toMilliseconds()); * cacheAdapterTestSuite({ * createAdapter: () => * new RedisCacheAdapter({ * database: client, * serde: new Serde(new SuperJsonSerdeAdapter()), * }), * test, * beforeEach, * expect, * describe, * }); * }); * ``` */ export function cacheAdapterTestSuite(settings) { const { expect, test, createAdapter, describe, beforeEach: beforeEach_, delayBuffer = TimeSpan.fromMilliseconds(10), context = new ExecutionContext(new NoOpExecutionContextAdapter()), } = settings; let adapter; beforeEach_(async () => { adapter = (await createAdapter()); }); async function delayWithBuffer(ttl) { await delay(TimeSpan.fromTimeSpan(ttl).addTimeSpan(delayBuffer)); } const TTL = TimeSpan.fromMilliseconds(50); describe("ICacheAdapter tests:", () => { describe("method: get", () => { test("Should return the value when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(1); }); test("Should return null when keys doesnt exists", async () => { expect(await adapter.get(context, "a")).toBeNull(); }); test("Should return null when key is experied", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); expect(await adapter.get(context, "a")).toBeNull(); }); }); describe("method: getAndRemove", () => { test("Should return value when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.getAndRemove(context, "a")).toBe(1); }); test("Should return null when key doesnt exists", async () => { expect(await adapter.getAndRemove(context, "a")).toBeNull(); }); test("Should return null when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); expect(await adapter.getAndRemove(context, "a")).toBeNull(); }); test("Should persist removal when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); await adapter.getAndRemove(context, "a"); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBeNull(); }); }); describe("method: add", () => { test("Should return true when key doesnt exists", async () => { const result = await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(result).toBe(true); }); test("Should return true when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); expect(await adapter.add(context, "a", 1, null)).toBe(true); }); test("Should persist values when key doesnt exist", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(1); }); test("Should persist values when key is expired", async () => { await adapter.add(context, "a", -1, TTL); await delayWithBuffer(TTL); await adapter.add(context, "a", 1, null); expect(await adapter.get(context, "a")).toBe(1); }); test("Should return false when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.add(context, "a", 1, null)).toBe(false); }); test("Should not persist value when key exist", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); await adapter.add(context, "a", 2, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(1); }); }); describe("method: put", () => { test("Should return true when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.put(context, "a", -1, null)).toBe(true); }); test("Should persist value when key exist", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); await adapter.put(context, "a", -1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(-1); }); test("Should return false when key doesnt exists", async () => { expect(await adapter.put(context, "a", -1, null)).toBe(false); }); test("Should return false when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); expect(await adapter.put(context, "a", -1, null)).toBe(false); }); test("Should persist values when key doesnt exist", async () => { await adapter.put(context, "a", -1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(-1); }); test("Should persist values when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); await adapter.put(context, "a", -1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(-1); }); test("Should replace the ttl value", async () => { const ttlA = TimeSpan.fromMilliseconds(100); await adapter.add(context, "a", 1, ttlA); await delayWithBuffer(TTL.divide(4)); const ttlB = TimeSpan.fromMilliseconds(50); await adapter.put(context, "a", -1, ttlB); await delayWithBuffer(ttlB); expect(await adapter.get(context, "a")).toBeNull(); }); }); describe("method: update", () => { test("Should return true when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.update(context, "a", -1)).toBe(true); }); test("Should persist value when key exist", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); await adapter.update(context, "a", -1); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(-1); }); test("Should return false when key doesnt exists", async () => { expect(await adapter.update(context, "a", -1)).toBe(false); }); test("Should return false when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); expect(await adapter.update(context, "a", -1)).toBe(false); }); test("Should not persist value when key doesnt exist", async () => { await adapter.update(context, "a", -1); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBeNull(); }); test("Should not persist value when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); await adapter.update(context, "a", -1); expect(await adapter.get(context, "a")).toBeNull(); }); }); describe("method: increment", () => { test("Should return true when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); expect(await adapter.increment(context, "a", 1)).toBe(true); }); test("Should persist increment when key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); await adapter.increment(context, "a", 1); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBe(2); }); test("Should return false when key doesnt exists", async () => { expect(await adapter.increment(context, "a", 1)).toBe(false); }); test("Should return false when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); expect(await adapter.increment(context, "a", 1)).toBe(false); }); test("Should not persist increment when key doesnt exists", async () => { await adapter.increment(context, "a", 1); await delayWithBuffer(TTL.divide(4)); expect(await adapter.get(context, "a")).toBeNull(); }); test("Should not persist increment when key is expired", async () => { await adapter.add(context, "a", 1, TTL); await delayWithBuffer(TTL); await adapter.increment(context, "a", 1); expect(await adapter.get(context, "a")).toBeNull(); }); test("Should throw TypeError when value is not number type", async () => { await adapter.add(context, "a", "str", null); await delayWithBuffer(TTL.divide(4)); await expect(adapter.increment(context, "a", 1)).rejects.toBeInstanceOf(TypeError); }); }); describe("method: removeMany", () => { test("Should return false when all keys does not exists", async () => { const result = await adapter.removeMany(context, [ "a", "b", "c", ]); expect(result).toBe(false); }); test("Should return true when one key exists", async () => { await adapter.add(context, "a", 1, null); await delayWithBuffer(TTL.divide(4)); const result = await adapter.removeMany(context, [ "a", "b", "c", ]); expect(result).toBe(true); }); test("Should persist removal of the keys that exists", async () => { await adapter.add(context, "a", 1, null); await adapter.add(context, "b", 2, null); await adapter.add(context, "c", 3, null); await delayWithBuffer(TTL.divide(4)); await adapter.removeMany(context, ["a", "b"]); await delayWithBuffer(TTL.divide(4)); const result = [ await adapter.get(context, "a"), await adapter.get(context, "b"), await adapter.get(context, "c"), ]; expect(result).toEqual([null, null, 3]); }); }); describe("method: removeAll", () => { test("Should remove all keys", async () => { await adapter.add(context, "cache/a", 1, null); await adapter.add(context, "cache/b", 2, null); await adapter.add(context, "c", 3, null); await delayWithBuffer(TTL.divide(4)); await adapter.removeAll(context); await delayWithBuffer(TTL.divide(4)); expect([ await adapter.get(context, "cache/a"), await adapter.get(context, "cache/b"), await adapter.get(context, "c"), ]).toEqual([null, null, null]); }); }); describe("method: removeByKeyPrefix", () => { test(`Should remove all keys that start with prefix "cache"`, async () => { await adapter.add(context, "cache/a", 1, null); await adapter.add(context, "cache/b", 2, null); await adapter.add(context, "c", 3, null); await delayWithBuffer(TTL.divide(4)); await adapter.removeByKeyPrefix(context, "cache"); await delayWithBuffer(TTL.divide(4)); const result = [ await adapter.get(context, "cache/a"), await adapter.get(context, "cache/b"), await adapter.get(context, "c"), ]; expect(result).toEqual([null, null, 3]); }); }); }); } //# sourceMappingURL=cache-adapter.test-suite.js.map