import { describe, expect, it } from "@effect/vitest"; import { Effect, Exit, Layer } from "effect"; import { ApiKeyService, ApiKeyValidationError, makeCachedApiKeyValidate, type ApiKeyOwner, } from "./api-keys"; import { WorkOSClient, type WorkOSClientService } from "./workos"; const stubWorkOS = (overrides: Partial) => Layer.succeed( WorkOSClient, new Proxy({} as WorkOSClientService, { get: (_target, prop) => { if (prop in overrides) return overrides[prop as keyof WorkOSClientService]; return () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`); }, }), ); const userOwner = (keyId: string): ApiKeyOwner => ({ scope: "user", accountId: "user_123", organizationId: "org_123", keyId, }); describe("makeCachedApiKeyValidate", () => { it.effect("serves a repeat validation from cache without a second upstream call", () => Effect.gen(function* () { let calls = 0; const cached = makeCachedApiKeyValidate(() => Effect.sync(() => { calls += 1; return userOwner("api_key_1"); }), ); const first = yield* cached.validate("sk_test_abc"); const second = yield* cached.validate("sk_test_abc"); expect(calls).toBe(1); expect(first).toEqual(userOwner("api_key_1")); expect(second).toEqual(first); }), ); it.effect("re-validates once the TTL has expired", () => Effect.gen(function* () { let calls = 0; let nowMs = 0; const cached = makeCachedApiKeyValidate( () => Effect.sync(() => { calls += 1; return userOwner("api_key_1"); }), { ttlMs: 1_000, now: () => nowMs }, ); yield* cached.validate("sk_test_abc"); nowMs = 999; yield* cached.validate("sk_test_abc"); expect(calls).toBe(1); nowMs = 1_000; yield* cached.validate("sk_test_abc"); expect(calls).toBe(2); }), ); it.effect("never caches an invalid key", () => Effect.gen(function* () { let calls = 0; const cached = makeCachedApiKeyValidate(() => Effect.sync(() => { calls += 1; return null; }), ); const first = yield* cached.validate("sk_test_bad"); const second = yield* cached.validate("sk_test_bad"); expect(first).toBeNull(); expect(second).toBeNull(); expect(calls).toBe(2); expect(cached.cacheKeys()).toHaveLength(0); }), ); it.effect("never caches an upstream failure", () => Effect.gen(function* () { let calls = 0; const cached = makeCachedApiKeyValidate(() => Effect.suspend(() => { calls += 1; return Effect.fail(new ApiKeyValidationError({ cause: "workos_down" })); }), ); const first = yield* Effect.exit(cached.validate("sk_test_abc")); const second = yield* Effect.exit(cached.validate("sk_test_abc")); expect(Exit.isFailure(first)).toBe(true); expect(Exit.isFailure(second)).toBe(true); expect(calls).toBe(2); expect(cached.cacheKeys()).toHaveLength(0); }), ); it.effect("resets a saturated map of live entries rather than growing past the bound", () => Effect.gen(function* () { const cached = makeCachedApiKeyValidate((value) => Effect.succeed(userOwner(value)), { maxEntries: 3, }); for (const value of ["sk_1", "sk_2", "sk_3"]) { yield* cached.validate(value); } expect(cached.cacheKeys()).toHaveLength(3); // Nothing is expired, so a fourth live entry resets the map instead of // growing it (mirrors execution-gate's writeCache). yield* cached.validate("sk_4"); expect(cached.cacheKeys()).toHaveLength(1); yield* cached.validate("sk_5"); expect(cached.cacheKeys()).toHaveLength(2); }), ); it.effect("evicts expired entries before resetting a saturated map", () => Effect.gen(function* () { let nowMs = 0; const cached = makeCachedApiKeyValidate((value) => Effect.succeed(userOwner(value)), { ttlMs: 1_000, maxEntries: 3, now: () => nowMs, }); for (const value of ["sk_1", "sk_2", "sk_3"]) { yield* cached.validate(value); } expect(cached.cacheKeys()).toHaveLength(3); // Everything is expired: the sweep drops the stale entries and the new // one fits without a full reset. nowMs = 2_000; yield* cached.validate("sk_4"); expect(cached.cacheKeys()).toHaveLength(1); }), ); it.effect("keys the cache by the SHA-256 digest, never the raw key value", () => Effect.gen(function* () { const rawKey = "sk_live_super_secret_value"; const cached = makeCachedApiKeyValidate(() => Effect.succeed(userOwner("api_key_1"))); yield* cached.validate(rawKey); const expectedDigest = yield* Effect.promise(async () => { const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(rawKey)); return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0"), ).join(""); }); expect(cached.cacheKeys()).toEqual([expectedDigest]); expect(cached.cacheKeys()).not.toContain(rawKey); }), ); }); describe("ApiKeyService.WorkOS validation cache", () => { it.effect("validates a repeated key through one upstream call per layer build", () => Effect.gen(function* () { let calls = 0; const layer = ApiKeyService.WorkOS.pipe( Layer.provide( stubWorkOS({ validateApiKey: () => Effect.sync(() => { calls += 1; return { apiKey: { id: "api_key_123", owner: { type: "user", id: "user_123", organizationId: "org_123" }, }, }; }), }), ), ); const { first, second } = yield* Effect.gen(function* () { const apiKeys = yield* ApiKeyService; return { first: yield* apiKeys.validate("sk_test_abc"), second: yield* apiKeys.validate("sk_test_abc"), }; }).pipe(Effect.provide(layer)); expect(calls).toBe(1); expect(first?.keyId).toBe("api_key_123"); expect(second).toEqual(first); }), ); });