import { describe, test, expect } from "bun:test"; import { computeProductsCacheTTL, PRODUCTS_TTL_HOT, PRODUCTS_TTL_LAZY, WON_LOST_STATUS_IDS, } from "../../src/modules/algorithms/computeProductsCacheTTL"; const NOW = new Date("2026-03-02T12:00:00Z"); const DAY_MS = 24 * 60 * 60 * 1000; describe("computeProductsCacheTTL", () => { // -- Constants ---------------------------------------------------------- test("PRODUCTS_TTL_HOT is 45 seconds", () => { expect(PRODUCTS_TTL_HOT).toBe(45_000); }); test("PRODUCTS_TTL_LAZY is 20 minutes", () => { expect(PRODUCTS_TTL_LAZY).toBe(1_200_000); }); // -- Won/Lost status set ------------------------------------------------ test("WON_LOST_STATUS_IDS contains Won canonical ID (29)", () => { expect(WON_LOST_STATUS_IDS.has(29)).toBe(true); }); test("WON_LOST_STATUS_IDS contains Lost canonical ID (53) and Canceled (59)", () => { expect(WON_LOST_STATUS_IDS.has(53)).toBe(true); expect(WON_LOST_STATUS_IDS.has(59)).toBe(true); }); test("WON_LOST_STATUS_IDS does not contain Pending Won (49) or Pending Lost (50)", () => { // Pending Won/Lost do not have wonFlag/lostFlag set in QuoteStatuses expect(WON_LOST_STATUS_IDS.has(49)).toBe(false); expect(WON_LOST_STATUS_IDS.has(50)).toBe(false); }); test("WON_LOST_STATUS_IDS does not contain Active (58) or New (24)", () => { expect(WON_LOST_STATUS_IDS.has(58)).toBe(false); expect(WON_LOST_STATUS_IDS.has(24)).toBe(false); }); // -- Rule 1: Won/Lost/Pending Won/Lost → null -------------------------- test("returns null for Won status (CW ID 29)", () => { const result = computeProductsCacheTTL({ statusCwId: 29, closedFlag: true, closedDate: new Date(NOW.getTime() - 2 * DAY_MS), expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS), now: NOW, }); expect(result).toBeNull(); }); test("returns PRODUCTS_TTL_HOT for Pending Won status (CW ID 49) with recent activity", () => { // Pending Won is not in WON_LOST_STATUS_IDS (no wonFlag), so it falls // through to the activity-based rules. const result = computeProductsCacheTTL({ statusCwId: 49, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_HOT); }); test("returns null for Lost status (CW ID 53)", () => { const result = computeProductsCacheTTL({ statusCwId: 53, closedFlag: true, closedDate: new Date(NOW.getTime() - 5 * DAY_MS), expectedCloseDate: null, lastUpdated: null, now: NOW, }); expect(result).toBeNull(); }); test("returns PRODUCTS_TTL_HOT for Pending Lost status (CW ID 50) with recent activity", () => { // Pending Lost is not in WON_LOST_STATUS_IDS (no lostFlag), so it falls // through to the activity-based rules. const result = computeProductsCacheTTL({ statusCwId: 50, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_HOT); }); // -- Rule 2: Opp not cacheable → null ---------------------------------- test("returns null when opp is closed > 30 days (main cache null)", () => { const result = computeProductsCacheTTL({ statusCwId: 58, // Active — but closed flag overrides closedFlag: true, closedDate: new Date(NOW.getTime() - 60 * DAY_MS), expectedCloseDate: null, lastUpdated: null, now: NOW, }); expect(result).toBeNull(); }); // -- Rule 3: Updated within 3 days → 15s ------------------------------- test("returns PRODUCTS_TTL_HOT when lastUpdated is within 3 days", () => { const result = computeProductsCacheTTL({ statusCwId: 58, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_HOT); }); test("returns PRODUCTS_TTL_HOT when lastUpdated is exactly 3 days ago", () => { const result = computeProductsCacheTTL({ statusCwId: 58, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 3 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_HOT); }); // -- Rule 4: Everything else → 30 min ---------------------------------- test("returns PRODUCTS_TTL_LAZY when lastUpdated is > 3 days ago", () => { const result = computeProductsCacheTTL({ statusCwId: 58, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 10 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_LAZY); }); test("returns PRODUCTS_TTL_LAZY when no lastUpdated is set", () => { const result = computeProductsCacheTTL({ statusCwId: 58, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: null, now: NOW, }); expect(result).toBe(PRODUCTS_TTL_LAZY); }); test("returns PRODUCTS_TTL_LAZY for recently-closed (within 30 days) non-won/lost", () => { // Edge case: closedFlag true, but status is not Won/Lost (unusual but possible) const result = computeProductsCacheTTL({ statusCwId: 56, // Internal Review (not won/lost) closedFlag: true, closedDate: new Date(NOW.getTime() - 10 * DAY_MS), expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 20 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_LAZY); }); // -- Rule priority: Won/Lost takes priority over recent activity -------- test("Won status takes priority even with very recent lastUpdated", () => { const result = computeProductsCacheTTL({ statusCwId: 29, closedFlag: true, closedDate: new Date(NOW.getTime() - 1 * DAY_MS), expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 1000), // 1 second ago now: NOW, }); expect(result).toBeNull(); }); // -- Null statusCwId (should not skip rule 1) --------------------------- test("null statusCwId falls through to other rules", () => { const result = computeProductsCacheTTL({ statusCwId: null, closedFlag: false, closedDate: null, expectedCloseDate: null, lastUpdated: new Date(NOW.getTime() - 1 * DAY_MS), now: NOW, }); expect(result).toBe(PRODUCTS_TTL_HOT); }); });