ttl-map.test.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
  2. import { TTLMap } from "@/lib/cache/ttl-map";
  3. describe("TTLMap", () => {
  4. beforeEach(() => {
  5. vi.useFakeTimers();
  6. });
  7. afterEach(() => {
  8. vi.useRealTimers();
  9. });
  10. it("should return undefined for missing key", () => {
  11. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  12. expect(map.get("missing")).toBeUndefined();
  13. });
  14. it("should store and retrieve a value", () => {
  15. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  16. map.set("a", 42);
  17. expect(map.get("a")).toBe(42);
  18. expect(map.size).toBe(1);
  19. });
  20. it("should return undefined for expired key", () => {
  21. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  22. map.set("a", 42);
  23. vi.advanceTimersByTime(1001);
  24. expect(map.get("a")).toBeUndefined();
  25. });
  26. it("should not return expired key via has()", () => {
  27. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  28. map.set("a", 42);
  29. vi.advanceTimersByTime(1001);
  30. expect(map.has("a")).toBe(false);
  31. });
  32. it("should bump LRU order on get", () => {
  33. const map = new TTLMap<string, number>({ ttlMs: 10000, maxSize: 3 });
  34. map.set("a", 1);
  35. map.set("b", 2);
  36. map.set("c", 3);
  37. // Access "a" to bump it (it was oldest)
  38. map.get("a");
  39. // Insert "d" - should evict "b" (oldest after bump), not "a"
  40. map.set("d", 4);
  41. expect(map.has("a")).toBe(true);
  42. expect(map.has("b")).toBe(false);
  43. expect(map.has("c")).toBe(true);
  44. expect(map.has("d")).toBe(true);
  45. });
  46. it("should evict expired entries first when at capacity", () => {
  47. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 3 });
  48. map.set("a", 1);
  49. map.set("b", 2);
  50. map.set("c", 3);
  51. // Expire all entries
  52. vi.advanceTimersByTime(1001);
  53. // Should evict expired entries, making room
  54. map.set("d", 4);
  55. expect(map.size).toBe(1);
  56. expect(map.get("d")).toBe(4);
  57. });
  58. it("should evict oldest 10% when at capacity with no expired entries", () => {
  59. const map = new TTLMap<string, number>({ ttlMs: 100000, maxSize: 10 });
  60. for (let i = 0; i < 10; i++) {
  61. map.set(`key-${i}`, i);
  62. }
  63. expect(map.size).toBe(10);
  64. // Insert one more - should evict at least 1 (10% of 10 = 1)
  65. map.set("new", 99);
  66. expect(map.size).toBeLessThanOrEqual(10);
  67. expect(map.get("new")).toBe(99);
  68. // Oldest key should be evicted
  69. expect(map.has("key-0")).toBe(false);
  70. });
  71. it("should delete an existing key", () => {
  72. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  73. map.set("a", 1);
  74. expect(map.delete("a")).toBe(true);
  75. expect(map.get("a")).toBeUndefined();
  76. expect(map.size).toBe(0);
  77. });
  78. it("should return false when deleting non-existent key", () => {
  79. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  80. expect(map.delete("missing")).toBe(false);
  81. });
  82. it("should update existing key with new value and reset TTL", () => {
  83. const map = new TTLMap<string, number>({ ttlMs: 1000, maxSize: 10 });
  84. map.set("a", 1);
  85. vi.advanceTimersByTime(800);
  86. map.set("a", 2);
  87. vi.advanceTimersByTime(800);
  88. // Should still be alive (TTL was reset on second set)
  89. expect(map.get("a")).toBe(2);
  90. });
  91. });