pingql/apps/pay/src/monitor.test.ts

221 lines
9.0 KiB
TypeScript

import { test, expect, describe } from "bun:test";
import { computeApplyPlan, computeExpiry, insertIntoStack } from "./monitor";
const day = 86400000;
const now = new Date("2026-03-24T00:00:00Z");
function daysFromNow(d: number) { return new Date(now.getTime() + d * day); }
describe("insertIntoStack", () => {
test("inserts into empty stack", () => {
expect(insertIntoStack([], { plan: "pro", remaining_days: 20 }))
.toEqual([{ plan: "pro", remaining_days: 20 }]);
});
test("maintains tier-descending order", () => {
const stack = [{ plan: "pro2x", remaining_days: 10 }];
expect(insertIntoStack(stack, { plan: "pro4x", remaining_days: 5 }))
.toEqual([{ plan: "pro4x", remaining_days: 5 }, { plan: "pro2x", remaining_days: 10 }]);
});
test("inserts lower tier after higher", () => {
const stack = [{ plan: "pro2x", remaining_days: 10 }];
expect(insertIntoStack(stack, { plan: "pro", remaining_days: 30 }))
.toEqual([{ plan: "pro2x", remaining_days: 10 }, { plan: "pro", remaining_days: 30 }]);
});
test("merges same plan by adding days", () => {
const stack = [{ plan: "pro", remaining_days: 20 }];
expect(insertIntoStack(stack, { plan: "pro", remaining_days: 30 }))
.toEqual([{ plan: "pro", remaining_days: 50 }]);
});
test("merges same plan - null wins (lifetime)", () => {
const stack = [{ plan: "lifetime", remaining_days: null }];
expect(insertIntoStack(stack, { plan: "lifetime", remaining_days: null }))
.toEqual([{ plan: "lifetime", remaining_days: null }]);
});
test("merges timed into permanent → permanent", () => {
const stack = [{ plan: "pro", remaining_days: 20 }];
// This shouldn't happen in practice but tests null-wins logic
expect(insertIntoStack(stack, { plan: "pro", remaining_days: null }))
.toEqual([{ plan: "pro", remaining_days: null }]);
});
});
describe("computeApplyPlan", () => {
test("free user buys Pro 1mo", () => {
const acc = { plan: "free", plan_expires_at: null, plan_stack: [] };
const result = computeApplyPlan(acc, { plan: "pro", months: 1 }, now);
expect(result.plan).toBe("pro");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 30 * day);
expect(result.plan_stack).toEqual([]);
});
test("pro user renews same plan", () => {
const expires = daysFromNow(20);
const acc = { plan: "pro", plan_expires_at: expires, plan_stack: [] };
const result = computeApplyPlan(acc, { plan: "pro", months: 1 }, now);
expect(result.plan).toBe("pro");
expect(result.plan_expires_at!.getTime()).toBe(expires.getTime() + 30 * day);
expect(result.plan_stack).toEqual([]);
});
test("pro (20d left) upgrades to pro2x 1mo", () => {
const acc = { plan: "pro", plan_expires_at: daysFromNow(20), plan_stack: [] };
const result = computeApplyPlan(acc, { plan: "pro2x", months: 1 }, now);
expect(result.plan).toBe("pro2x");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 30 * day);
expect(result.plan_stack).toEqual([{ plan: "pro", remaining_days: 20 }]);
});
test("pro2x (10d left) upgrades to pro4x, existing pro base", () => {
const acc = {
plan: "pro2x", plan_expires_at: daysFromNow(10),
plan_stack: [{ plan: "pro", remaining_days: 20 }]
};
const result = computeApplyPlan(acc, { plan: "pro4x", months: 1 }, now);
expect(result.plan).toBe("pro4x");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 30 * day);
// pro2x (tier 2) should be before pro (tier 1) in stack
expect(result.plan_stack).toEqual([
{ plan: "pro2x", remaining_days: 10 },
{ plan: "pro", remaining_days: 20 },
]);
});
test("lifetime user buys pro2x 1mo", () => {
const acc = { plan: "lifetime", plan_expires_at: null, plan_stack: [] };
const result = computeApplyPlan(acc, { plan: "pro2x", months: 1 }, now);
expect(result.plan).toBe("pro2x");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 30 * day);
expect(result.plan_stack).toEqual([{ plan: "lifetime", remaining_days: null }]);
});
test("pro4x (15d left) buys lifetime - goes to stack", () => {
const acc = { plan: "pro4x", plan_expires_at: daysFromNow(15), plan_stack: [] };
const result = computeApplyPlan(acc, { plan: "lifetime", months: null }, now);
expect(result.plan).toBe("pro4x"); // stays active (higher tier)
expect(result.plan_expires_at!.getTime()).toBe(daysFromNow(15).getTime());
expect(result.plan_stack).toEqual([{ plan: "lifetime", remaining_days: null }]);
});
test("pro2x (10d left) buys pro 1mo with lifetime in stack", () => {
const acc = {
plan: "pro2x", plan_expires_at: daysFromNow(10),
plan_stack: [{ plan: "lifetime", remaining_days: null }]
};
const result = computeApplyPlan(acc, { plan: "pro", months: 1 }, now);
expect(result.plan).toBe("pro2x"); // stays active
expect(result.plan_stack).toEqual([
{ plan: "pro", remaining_days: 30 },
{ plan: "lifetime", remaining_days: null },
]);
});
test("expired pro activates new plan without saving expired", () => {
const acc = { plan: "pro", plan_expires_at: daysFromNow(-5), plan_stack: [] };
const result = computeApplyPlan(acc, { plan: "pro2x", months: 1 }, now);
expect(result.plan).toBe("pro2x");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 30 * day);
expect(result.plan_stack).toEqual([]);
});
test("free with existing stack - preserves stack", () => {
const acc = { plan: "free", plan_expires_at: null, plan_stack: [{ plan: "lifetime", remaining_days: null }] };
const result = computeApplyPlan(acc, { plan: "pro", months: 1 }, now);
expect(result.plan).toBe("pro");
expect(result.plan_stack).toEqual([{ plan: "lifetime", remaining_days: null }]);
});
test("buying same lower tier twice merges days", () => {
const acc = {
plan: "pro2x", plan_expires_at: daysFromNow(20),
plan_stack: [{ plan: "pro", remaining_days: 15 }]
};
const result = computeApplyPlan(acc, { plan: "pro", months: 1 }, now);
expect(result.plan).toBe("pro2x"); // stays
expect(result.plan_stack).toEqual([{ plan: "pro", remaining_days: 45 }]); // 15 + 30
});
});
describe("computeExpiry", () => {
test("returns null for non-pro plans", () => {
expect(computeExpiry({ plan: "free", plan_expires_at: null, plan_stack: [] }, now)).toBeNull();
expect(computeExpiry({ plan: "lifetime", plan_expires_at: null, plan_stack: [] }, now)).toBeNull();
});
test("returns null for non-expired pro", () => {
const acc = { plan: "pro", plan_expires_at: daysFromNow(10), plan_stack: [] };
expect(computeExpiry(acc, now)).toBeNull();
});
test("expired pro with empty stack → free", () => {
const acc = { plan: "pro", plan_expires_at: daysFromNow(-1), plan_stack: [] };
const result = computeExpiry(acc, now)!;
expect(result.plan).toBe("free");
expect(result.plan_expires_at).toBeNull();
expect(result.plan_stack).toEqual([]);
});
test("expired pro with lifetime in stack → promote lifetime", () => {
const acc = {
plan: "pro2x", plan_expires_at: daysFromNow(-1),
plan_stack: [{ plan: "lifetime", remaining_days: null }]
};
const result = computeExpiry(acc, now)!;
expect(result.plan).toBe("lifetime");
expect(result.plan_expires_at).toBeNull();
expect(result.plan_stack).toEqual([]);
});
test("expired pro with timed base → promote with computed expiry", () => {
const acc = {
plan: "pro2x", plan_expires_at: daysFromNow(-1),
plan_stack: [{ plan: "pro", remaining_days: 20 }]
};
const result = computeExpiry(acc, now)!;
expect(result.plan).toBe("pro");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 20 * day);
expect(result.plan_stack).toEqual([]);
});
test("expired pro with multi-layer stack → promotes top, keeps rest", () => {
const acc = {
plan: "pro4x", plan_expires_at: daysFromNow(-1),
plan_stack: [
{ plan: "pro2x", remaining_days: 15 },
{ plan: "lifetime", remaining_days: null },
]
};
const result = computeExpiry(acc, now)!;
expect(result.plan).toBe("pro2x");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 15 * day);
expect(result.plan_stack).toEqual([{ plan: "lifetime", remaining_days: null }]);
});
test("expired pro skips zero-day layers", () => {
const acc = {
plan: "pro4x", plan_expires_at: daysFromNow(-1),
plan_stack: [
{ plan: "pro2x", remaining_days: 0 },
{ plan: "pro", remaining_days: 10 },
]
};
const result = computeExpiry(acc, now)!;
expect(result.plan).toBe("pro");
expect(result.plan_expires_at!.getTime()).toBe(now.getTime() + 10 * day);
expect(result.plan_stack).toEqual([]);
});
test("expired pro with all zero-day layers → free", () => {
const acc = {
plan: "pro", plan_expires_at: daysFromNow(-1),
plan_stack: [{ plan: "pro2x", remaining_days: 0 }]
};
const result = computeExpiry(acc, now)!;
expect(result.plan).toBe("free");
expect(result.plan_stack).toEqual([]);
});
});