import { describe, expect, it } from "vitest"; import { Timestamp } from "firebase-admin/firestore"; import { toGameDto } from "../../src/types/dto/predictionDto"; import { EMPTY_VOTE_HISTORY_DTO, toVoteHistoryDto } from "../../src/types/dto/statsDto"; import type { Game, VoteHistoryDoc } from "../../src/types/panit"; const UTC_ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; const ts = (iso: string) => Timestamp.fromDate(new Date(iso)); function wire(dto: unknown): Record { return JSON.parse(JSON.stringify(dto)); } function expectNoTimestampLeak(dto: unknown): void { const json = JSON.stringify(dto); expect(json).not.toContain("_seconds"); expect(json).not.toContain("_nanoseconds"); } const game = { gameId: "20260720LGHT0", time: ts("2026-07-20T09:30:00.000Z"), stadium: "잠실", status: "scheduled", homeTeamCode: "LG", awayTeamCode: "HT", } as Game & { gameId: string }; describe("toGameDto", () => { it("time 을 UTC ISO 문자열로 바꾼다", () => { const dto = toGameDto(game); expectNoTimestampLeak(dto); expect(dto.time).toMatch(UTC_ISO); expect(dto.time).toBe("2026-07-20T09:30:00.000Z"); }); it("경기가 끝나지 않았으면 결과 관련 키가 응답에 없다", () => { const json = wire(toGameDto(game)); expect(json).not.toHaveProperty("winningTeamCode"); expect(json).not.toHaveProperty("cancelReason"); }); it("문서에 없는 필드를 임의로 흘려보내지 않는다", () => { const polluted = { ...game, internalOnly: "secret" } as Game & { gameId: string }; expect(wire(toGameDto(polluted))).not.toHaveProperty("internalOnly"); }); }); describe("toVoteHistoryDto", () => { const settled = { data: [ { gameId: "20260720LGHT0", team: "LG", result: true }, { gameId: "20260720SSKT0", team: "SS", cancelled: true }, ], judgment: "success", correctCount: 1, completedCount: 2, streakAfter: 3, rewardSettledAt: ts("2026-07-21T00:15:00.000Z"), rewardTotal: 70, } as VoteHistoryDoc; it("rewardSettledAt 을 UTC ISO 문자열로 바꾼다", () => { const dto = toVoteHistoryDto(settled); expectNoTimestampLeak(dto); expect(dto.rewardSettledAt).toMatch(UTC_ISO); expect(dto.rewardSettledAt).toBe("2026-07-21T00:15:00.000Z"); }); it("정산 전이면 rewardSettledAt 키가 응답에 없다", () => { const pending = { ...settled, rewardSettledAt: undefined, rewardTotal: undefined } as VoteHistoryDoc; const json = wire(toVoteHistoryDto(pending)); expect(json).not.toHaveProperty("rewardSettledAt"); expect(json).not.toHaveProperty("rewardTotal"); expect(json.data).toHaveLength(2); }); it("문서가 없는 날은 빈 목록으로 응답한다", () => { expect(wire(EMPTY_VOTE_HISTORY_DTO)).toEqual({ data: [] }); }); it("투표 항목의 미지 필드를 흘려보내지 않는다", () => { const polluted = { ...settled, data: [{ gameId: "g1", team: "LG", result: true, internalOnly: "secret" }], } as VoteHistoryDoc; expect(JSON.stringify(toVoteHistoryDto(polluted))).not.toContain("internalOnly"); }); });