Files
vyndr/tests/unit/billingDisplay.test.js
T
builtbykev b6787af191 Wave 1: kill three trust bugs (billing renewal + namesake collision + Desk copy)
FIX 1 — Honest billing renewal render. VYNDR tiers are monthly, so a
`subscription_end` far in the future (the manually-seeded "RENEWS 6/9/2036"
founder row) is a comped/lifetime/seed value, not a renewal. New
web/src/lib/billingDisplay.js `classifyRenewal()` → date | none | lapsed |
unknown (strict Date.parse guard, MONTHLY_RENEWAL_MAX_DAYS=60). Profile page
renders the classified label for both the "Renews" stat and the
cancel-scheduled "Access ends" line — no raw far-future date. No DB row mutated.

FIX 2 — MLB namesake collision (James Wood → "Chicago Cubs"). searchPlayer now
collects ALL exact-nameKey matches instead of first-`.find`; a ≥2 collision
resolves ONLY via a confident teamHint (the prop's game participants, matched
against the cached /teams list with ESPN↔statsapi abbr reconciliation), else
refuses (null) — never guesses. The hint threads getPlayerStats →
resolvePlayerStats → snapshotService (built from each prop's home/away team).
Join invariant: a single-exact player whose team isn't in the hinted game has
its team DROPPED (null), so streaks/rosterlogs never tag a foreign team. Full
teamHint recovery shipped (not just the refuse fallback).

FIX 3 — DeskShowcase headline "A $1M terminal." → deadpan value-showing copy
"Every grade, every alt line, live." Prices ($44.99 / $34.99) unchanged.

Tests: billingDisplay.test.js (7), mlbNamesakeResolve.test.js (12,
disambiguation + join invariant + pure helpers), ds5PricingStates updated to
assert the new headline and no "$1M". Full suite green (237 suites / 2863
tests); web `next build` exit 0.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 03:48:58 -04:00

49 lines
2.0 KiB
JavaScript

// Wave 1 trust bug — honest renewal render. classifyRenewal must never let a
// far-future / comped `subscription_end` (the "RENEWS 6/9/2036" lie) render as
// a real monthly renewal, and must fail closed to `unknown` on bad input.
const { classifyRenewal, MONTHLY_RENEWAL_MAX_DAYS } = require('../../web/src/lib/billingDisplay');
const NOW = Date.parse('2026-07-13T00:00:00.000Z');
const DAY = 86_400_000;
describe('classifyRenewal — honest billing render', () => {
test('MONTHLY_RENEWAL_MAX_DAYS is 60', () => {
expect(MONTHLY_RENEWAL_MAX_DAYS).toBe(60);
});
test('the 2036 comped/seed row is NOT a renewal → none', () => {
const r = classifyRenewal('2036-06-09T00:00:00.000Z', NOW);
expect(r.kind).toBe('none');
expect(r.iso).toBeUndefined();
});
test('a plausible monthly next-bill (now + 30d) → date, carries iso', () => {
const r = classifyRenewal(new Date(NOW + 30 * DAY).toISOString(), NOW);
expect(r.kind).toBe('date');
expect(typeof r.iso).toBe('string');
expect(Date.parse(r.iso)).toBe(NOW + 30 * DAY);
});
test('absent value (null / undefined / empty) → unknown', () => {
expect(classifyRenewal(null, NOW).kind).toBe('unknown');
expect(classifyRenewal(undefined, NOW).kind).toBe('unknown');
expect(classifyRenewal('', NOW).kind).toBe('unknown');
});
test('a renewal well in the past → lapsed', () => {
const r = classifyRenewal(new Date(NOW - 10 * DAY).toISOString(), NOW);
expect(r.kind).toBe('lapsed');
});
test('malformed date string → unknown (never coerced to an epoch date)', () => {
expect(classifyRenewal('not a date', NOW).kind).toBe('unknown');
expect(classifyRenewal('N/A', NOW).kind).toBe('unknown');
});
test('exactly at the 60-day boundary still reads as a date; just beyond → none', () => {
expect(classifyRenewal(new Date(NOW + 60 * DAY).toISOString(), NOW).kind).toBe('date');
expect(classifyRenewal(new Date(NOW + 61 * DAY).toISOString(), NOW).kind).toBe('none');
});
});