import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { dayTtlMs } from "../../src/repositories/kboRepository"; import type { ScheduleGame, GameStatus } from "../../src/kbo/schedule"; const ONE_DAY_MS = 24 * 60 * 60 * 1000; const SEVEN_DAYS_MS = 7 * ONE_DAY_MS; const SIX_HOURS_MS = 6 * 60 * 60 * 1000; const THIRTY_SEC_MS = 30_000; function game(status: GameStatus, time = "18:00"): ScheduleGame { return { status, time } as unknown as ScheduleGame; } /** yyyymmdd 문자열의 로컬 자정 타임스탬프. dayTtlMs 와 같은 기준. */ function localDayStart(yyyymmdd: string): number { const y = Number(yyyymmdd.slice(0, 4)); const m = Number(yyyymmdd.slice(4, 6)); const d = Number(yyyymmdd.slice(6, 8)); return new Date(y, m - 1, d).getTime(); } describe("dayTtlMs", () => { beforeEach(() => { vi.useFakeTimers(); }); afterEach(() => { vi.useRealTimers(); }); describe("모레 이후", () => { it("TTL이 해당 날짜 자정을 넘기지 않는다", () => { // 회귀 테스트: 2026-07-27 에 담긴 08-01 스냅샷이 7d TTL로 08-03까지 // 살아남아, 폭염취소된 경기가 달력에서 계속 `scheduled` 로 보였다. vi.setSystemTime(new Date(2026, 6, 27, 21, 6)); const ttl = dayTtlMs("20260801", [game("scheduled")]); expect(Date.now() + ttl).toBe(localDayStart("20260801")); expect(ttl).toBeLessThan(SEVEN_DAYS_MS); }); it("7일보다 먼 날짜는 7d 로 상한을 둔다", () => { vi.setSystemTime(new Date(2026, 6, 27, 21, 6)); expect(dayTtlMs("20260901", [game("scheduled")])).toBe(SEVEN_DAYS_MS); }); it("자정 직전이어도 최소 30s 는 보장한다", () => { // 2026-08-01 23:59:59 → 08-03 자정까지 남은 시간은 0 에 가깝지 않지만, // 경계에서 0/음수 TTL 이 나오지 않는지 하한을 확인한다. vi.setSystemTime(new Date(2026, 7, 1, 23, 59, 59, 900)); expect(dayTtlMs("20260803", [game("scheduled")])) .toBeGreaterThanOrEqual(THIRTY_SEC_MS); }); }); it("내일은 6h", () => { vi.setSystemTime(new Date(2026, 7, 1, 10, 0)); expect(dayTtlMs("20260802", [game("scheduled")])).toBe(SIX_HOURS_MS); }); describe("지난 날짜", () => { it("모두 종료/취소면 7d", () => { vi.setSystemTime(new Date(2026, 7, 3, 10, 0)); const games = [game("completed"), game("cancelled")]; expect(dayTtlMs("20260801", games)).toBe(SEVEN_DAYS_MS); }); it("미종료 경기가 남아 있으면 30s", () => { vi.setSystemTime(new Date(2026, 7, 3, 10, 0)); const games = [game("completed"), game("scheduled")]; expect(dayTtlMs("20260801", games)).toBe(THIRTY_SEC_MS); }); }); describe("오늘", () => { it("첫 경기 시작 전이면 시작까지만 캐시한다", () => { vi.setSystemTime(new Date(2026, 7, 3, 17, 30)); // 18:00 시작 → 30분 expect(dayTtlMs("20260803", [game("scheduled", "18:00")])) .toBe(30 * 60_000); }); it("모두 끝났으면 7d", () => { vi.setSystemTime(new Date(2026, 7, 3, 23, 0)); expect(dayTtlMs("20260803", [game("completed")])).toBe(SEVEN_DAYS_MS); }); }); });