Files
optima/tests/unit/computeProductsCacheTTL.test.ts
T

198 lines
6.5 KiB
TypeScript

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);
});
});