import { beforeEach, describe, expect, it } from "vitest"; import { Timestamp } from "firebase-admin/firestore"; import { firestore } from "../../src/firebase"; import { listLedger } from "../../src/repositories/pointLedgerRepository"; import { getWallet } from "../../src/repositories/walletRepository"; import { toLedgerEntryDto, toWalletDto } from "../../src/types/dto/rewardDto"; import { PointLedgerType } from "../../src/types/points"; /** * 앱의 "포인트 내역" 화면 크래시 재현 방지. * * Firestore 에 실제로 저장된 Timestamp 를 읽어 응답 DTO 까지 태웠을 때 * 와이어에 `{_seconds,_nanoseconds}` 가 나오지 않는지 끝까지 확인한다. * (클라이언트는 이 필드를 `DateTime.parse(... as String)` 로 받으므로 * Map 이 오면 캐스트 예외로 화면 전체가 죽는다.) */ const uid = "ledger-wire-user"; const UTC_ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/; describe("포인트 내역 와이어 형식 (Firestore -> DTO)", () => { beforeEach(async () => { await firestore.recursiveDelete(firestore.doc(`users/${uid}`)); }); it("저장된 Timestamp 가 UTC ISO 문자열로 나가고 _seconds 가 남지 않는다", async () => { const createdAt = Timestamp.fromDate(new Date("2026-07-20T05:30:00.000Z")); await firestore.doc(`users/${uid}/pointLedger/tx-1`).set({ txId: "tx-1", uid, type: PointLedgerType.AttendanceDaily, op: "credit", amount: 20, availableBefore: 0, availableAfter: 20, reservedBefore: 0, reservedAfter: 0, relatedDate: "2026-07-20", createdAt, }); const page = await listLedger(uid, 20); expect(page.items).toHaveLength(1); const dto = page.items.map((entry) => toLedgerEntryDto(entry.id, uid, entry)); const json = JSON.stringify({ items: dto, cursor: page.cursor }); expect(json).not.toContain("_seconds"); expect(json).not.toContain("_nanoseconds"); expect(JSON.parse(json).items[0].createdAt).toBe("2026-07-20T05:30:00.000Z"); // 날짜 전용 필드는 형식이 유지되어야 한다 expect(JSON.parse(json).items[0].relatedDate).toBe("2026-07-20"); }); it("지갑 문서의 createdAt/updatedAt 도 UTC ISO 문자열로 나간다", async () => { const now = Timestamp.fromDate(new Date("2026-07-20T05:30:00.000Z")); await firestore.doc(`users/${uid}/wallet/current`).set({ availableBalance: 20, reservedBalance: 0, totalEarned: 20, totalSpent: 0, version: 1, createdAt: now, updatedAt: now, }); const wallet = await getWallet(uid); expect(wallet).not.toBeNull(); const json = JSON.stringify(toWalletDto(wallet!)); expect(json).not.toContain("_seconds"); expect(JSON.parse(json).createdAt).toMatch(UTC_ISO); expect(JSON.parse(json).updatedAt).toMatch(UTC_ISO); }); });