mmday-firebase/tests/repositories/ledgerWireFormat.test.ts
윤정민 2e4d01f985 Normalize pre-migration ledger documents in the response
- 포인트 시스템 개편(2026-07-16) 이전 스키마 문서가 운영 Firestore에 그대로 남아 있어 포인트 내역이 실패하던 문제 수정
- 구 문서에는 txId/uid/op/available*/reserved*가 없고 balanceAfter/refMonth/refDay가 있다
- 읽기 타입을 StoredLedgerEntry로 넓히고, DTO에서 남은 값으로 복원한다
  - txId는 문서 id, uid는 요청 uid, op는 type에서, 거래 전 잔액은 balanceAfter에서 역산
  - refMonth + refDay를 relatedDate(YYYY-MM-DD)로 합침
- 실제 운영 문서 형태를 그대로 쓴 회귀 테스트 추가
2026-07-20 20:50:57 +09:00

76 lines
2.8 KiB
TypeScript

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);
});
});