|
| 1 | +import { readFileSync } from "node:fs"; |
1 | 2 | import { describe, expect, it } from "vitest"; |
2 | | -import { TtlCache } from "../../../packages/discovery-index/src/cache"; |
| 3 | +import { DEFAULT_CACHE_MAX_ENTRIES, TtlCache } from "../../../packages/discovery-index/src/cache"; |
| 4 | + |
| 5 | +const SERVER_SOURCE = readFileSync("packages/discovery-index/src/server.ts", "utf8"); |
3 | 6 |
|
4 | 7 | function clock(startMs = 0) { |
5 | 8 | let now = startMs; |
@@ -65,4 +68,80 @@ describe("discovery-index TtlCache (#7164)", () => { |
65 | 68 | expect(await cache.getOrCompute("k", 100, compute)).toBe(2); |
66 | 69 | expect(calls).toBe(2); |
67 | 70 | }); |
| 71 | + |
| 72 | + describe("max-entry cap", () => { |
| 73 | + it("exports a positive default cap constant", () => { |
| 74 | + expect(Number.isInteger(DEFAULT_CACHE_MAX_ENTRIES)).toBe(true); |
| 75 | + expect(DEFAULT_CACHE_MAX_ENTRIES).toBeGreaterThan(0); |
| 76 | + }); |
| 77 | + |
| 78 | + it("with a cap of 2, a third distinct key evicts the oldest and stays at size 2", () => { |
| 79 | + const cache = new TtlCache<string>(Date.now, 2); |
| 80 | + cache.set("a", "1", 60_000); |
| 81 | + cache.set("b", "2", 60_000); |
| 82 | + cache.set("c", "3", 60_000); |
| 83 | + expect(cache.size).toBe(2); |
| 84 | + expect(cache.get("a")).toBeUndefined(); |
| 85 | + expect(cache.get("c")).toBe("3"); |
| 86 | + }); |
| 87 | + |
| 88 | + it("under the cap, set does not evict anything", () => { |
| 89 | + const cache = new TtlCache<string>(Date.now, 2); |
| 90 | + cache.set("a", "1", 60_000); |
| 91 | + expect(cache.size).toBe(1); |
| 92 | + expect(cache.get("a")).toBe("1"); |
| 93 | + }); |
| 94 | + |
| 95 | + it("evicts already-expired entries before falling back to oldest-inserted eviction", () => { |
| 96 | + const c = clock(); |
| 97 | + const cache = new TtlCache<string>(c.now, 2); |
| 98 | + cache.set("a", "1", 100); // will be expired |
| 99 | + c.advance(101); |
| 100 | + cache.set("b", "2", 60_000); // live |
| 101 | + cache.set("c", "3", 60_000); // expired-drop of "a" makes room, "b" survives |
| 102 | + expect(cache.size).toBe(2); |
| 103 | + expect(cache.get("a")).toBeUndefined(); |
| 104 | + expect(cache.get("b")).toBe("2"); |
| 105 | + expect(cache.get("c")).toBe("3"); |
| 106 | + }); |
| 107 | + |
| 108 | + it("REGRESSION: a key that is never re-read must not survive past the entry cap", () => { |
| 109 | + const cap = 10; |
| 110 | + const cache = new TtlCache<number>(Date.now, cap); |
| 111 | + for (let i = 0; i < cap + 50; i++) { |
| 112 | + cache.set(`key-${i}`, i, 60_000); |
| 113 | + expect(cache.size).toBeLessThanOrEqual(cap); |
| 114 | + } |
| 115 | + expect(cache.size).toBe(cap); |
| 116 | + }); |
| 117 | + |
| 118 | + it("overwriting an already-present key at the cap does not evict any other entry", () => { |
| 119 | + const cache = new TtlCache<string>(Date.now, 2); |
| 120 | + cache.set("a", "1", 60_000); |
| 121 | + cache.set("b", "2", 60_000); |
| 122 | + cache.set("b", "2-updated", 60_000); |
| 123 | + expect(cache.size).toBe(2); |
| 124 | + expect(cache.get("a")).toBe("1"); |
| 125 | + expect(cache.get("b")).toBe("2-updated"); |
| 126 | + }); |
| 127 | + |
| 128 | + it("falls back to the default cap when no cap is passed to the constructor", () => { |
| 129 | + const cache = new TtlCache<number>(); |
| 130 | + for (let i = 0; i < DEFAULT_CACHE_MAX_ENTRIES + 5; i++) { |
| 131 | + cache.set(`key-${i}`, i, 60_000); |
| 132 | + } |
| 133 | + expect(cache.size).toBe(DEFAULT_CACHE_MAX_ENTRIES); |
| 134 | + }); |
| 135 | + }); |
| 136 | + |
| 137 | + describe("server.ts wiring (#7164)", () => { |
| 138 | + it("passes an explicit cap to all three long-lived cache instances", () => { |
| 139 | + const explicitCapSites = [...SERVER_SOURCE.matchAll(/new TtlCache(?:<[^>]*>)?\([^)]*DEFAULT_CACHE_MAX_ENTRIES[^)]*\)/g)]; |
| 140 | + expect(explicitCapSites).toHaveLength(3); |
| 141 | + }); |
| 142 | + |
| 143 | + it("imports the cap constant from cache.ts", () => { |
| 144 | + expect(SERVER_SOURCE).toMatch(/import\s*\{[^}]*DEFAULT_CACHE_MAX_ENTRIES[^}]*\}\s*from\s*"\.\/cache\.js"/); |
| 145 | + }); |
| 146 | + }); |
68 | 147 | }); |
0 commit comments