mmday-firebase/tests/repositories/voteRepository.test.ts
윤정민 9daba193e4 Add draw prediction support to vote pipeline
- selectedTeamCode에 무승부 예약 코드 'DRAW' 허용 (sideOf가 draw 진영 반환)
- RTDB /votes/{gameId}/counts에 drawCount 추가, 투표 요약 응답에 포함
- 채점 규칙 변경: 무승부 경기는 'DRAW' 투표만 적중, 팀 투표는 오답 (기존: 전원 적중 처리)
- 무승부 경기(completed + winningTeamCode 없음)도 onGameCompleted 트리거에서 즉시 채점 (기존: dailyArchive 리컨실까지 지연)
- 어드민 수동 종료(POST /admin/game/end)에 winningTeamCode: 'DRAW' 지원
- VOTE_FLOW.md 갱신, voteRepository 무승부 투표/카운트 재분배 테스트 추가
2026-07-10 16:51:56 +09:00

178 lines
6.2 KiB
TypeScript

import { beforeEach, describe, expect, it } from "vitest";
import { rtdb } from "../../src/firebase";
import {
changeVote,
deleteGameVotes,
getAllUserVotes,
getCounts,
getUserDateVotes,
getUserVote,
submitVote,
} from "../../src/repositories/voteRepository";
import type { DateString } from "../../src/types/dateString";
const gameId = "20260412HTLG0";
const gameId2 = "20260412WOSK0";
const uid = "test-uid-1";
const uid2 = "test-uid-2";
const date = "2026-04-12" as DateString;
describe("voteRepository (RTDB)", () => {
beforeEach(async () => {
console.log("[setup] /votes, /userVotes 초기화");
await rtdb.ref("/votes").remove();
await rtdb.ref("/userVotes").remove();
});
describe("submitVote", () => {
it("home 투표 시 homeCount가 1 증가하고 인덱스가 저장된다", async () => {
await submitVote({ gameId, uid, date, side: "home", team: "LG" });
const counts = await getCounts(gameId);
const userVote = await getUserVote(gameId, uid);
const dateVotes = await getUserDateVotes(uid, date);
console.log("[home vote] counts:", counts);
console.log("[home vote] userVote:", userVote);
console.log("[home vote] dateVotes:", dateVotes);
expect(counts).toEqual({ homeCount: 1, awayCount: 0, drawCount: 0 });
expect(userVote).toEqual({ team: "LG" });
expect(dateVotes[gameId]).toEqual({ team: "LG" });
});
it("away 투표 시 awayCount만 증가한다", async () => {
await submitVote({ gameId, uid, date, side: "away", team: "KIA" });
const counts = await getCounts(gameId);
console.log("[away vote] counts:", counts);
expect(counts).toEqual({ homeCount: 0, awayCount: 1, drawCount: 0 });
});
it("무승부 투표 시 drawCount만 증가하고 team은 DRAW로 저장된다", async () => {
await submitVote({ gameId, uid, date, side: "draw", team: "DRAW" });
const counts = await getCounts(gameId);
const userVote = await getUserVote(gameId, uid);
console.log("[draw vote] counts:", counts);
expect(counts).toEqual({ homeCount: 0, awayCount: 0, drawCount: 1 });
expect(userVote).toEqual({ team: "DRAW" });
});
});
describe("changeVote", () => {
it("home→away 변경 시 카운트가 재분배되고 인덱스가 갱신된다", async () => {
await submitVote({ gameId, uid, date, side: "home", team: "LG" });
console.log("[change] 초기:", await getCounts(gameId));
await changeVote({
gameId,
uid,
date,
oldSide: "home",
newSide: "away",
newTeam: "KIA",
});
const counts = await getCounts(gameId);
const userVote = await getUserVote(gameId, uid);
const dateVotes = await getUserDateVotes(uid, date);
console.log("[change] 변경 후 counts:", counts);
console.log("[change] userVote:", userVote);
expect(counts).toEqual({ homeCount: 0, awayCount: 1, drawCount: 0 });
expect(userVote).toEqual({ team: "KIA" });
expect(dateVotes[gameId]).toEqual({ team: "KIA" });
});
it("home→draw 변경 시 drawCount로 재분배된다", async () => {
await submitVote({ gameId, uid, date, side: "home", team: "LG" });
await changeVote({
gameId,
uid,
date,
oldSide: "home",
newSide: "draw",
newTeam: "DRAW",
});
const counts = await getCounts(gameId);
const userVote = await getUserVote(gameId, uid);
console.log("[change→draw] counts:", counts);
expect(counts).toEqual({ homeCount: 0, awayCount: 0, drawCount: 1 });
expect(userVote).toEqual({ team: "DRAW" });
});
});
describe("getUserVote", () => {
it("존재하지 않으면 null을 반환한다", async () => {
const result = await getUserVote(gameId, "nonexistent-uid");
expect(result).toBeNull();
});
});
describe("getCounts", () => {
it("counts 노드가 없으면 0으로 채워 반환한다", async () => {
const result = await getCounts("nonexistent-game");
console.log("[counts-empty]", result);
expect(result).toEqual({ homeCount: 0, awayCount: 0, drawCount: 0 });
});
});
describe("getAllUserVotes", () => {
it("여러 유저의 투표를 맵으로 반환한다", async () => {
await submitVote({ gameId, uid, date, side: "home", team: "LG" });
await submitVote({ gameId, uid: uid2, date, side: "away", team: "KIA" });
const all = await getAllUserVotes(gameId);
console.log("[all votes]", all);
expect(Object.keys(all).sort()).toEqual([uid, uid2].sort());
expect(all[uid]).toEqual({ team: "LG" });
expect(all[uid2]).toEqual({ team: "KIA" });
});
it("투표가 없으면 빈 객체를 반환한다", async () => {
const result = await getAllUserVotes("nonexistent-game");
expect(result).toEqual({});
});
});
describe("getUserDateVotes", () => {
it("특정 날짜의 여러 경기 투표를 맵으로 반환한다", async () => {
await submitVote({ gameId, uid, date, side: "home", team: "LG" });
await submitVote({
gameId: gameId2,
uid,
date,
side: "away",
team: "키움",
});
const result = await getUserDateVotes(uid, date);
console.log("[date votes]", result);
expect(Object.keys(result).sort()).toEqual([gameId, gameId2].sort());
expect(result[gameId]).toEqual({ team: "LG" });
expect(result[gameId2]).toEqual({ team: "키움" });
});
});
describe("deleteGameVotes", () => {
it("/votes/{gameId}는 삭제되지만 /userVotes 인덱스는 남는다", async () => {
await submitVote({ gameId, uid, date, side: "home", team: "LG" });
await deleteGameVotes(gameId);
const counts = await getCounts(gameId);
const users = await getAllUserVotes(gameId);
const dateVotes = await getUserDateVotes(uid, date);
console.log("[delete] counts:", counts);
console.log("[delete] users:", users);
console.log("[delete] dateVotes (남아있음):", dateVotes);
expect(counts).toEqual({ homeCount: 0, awayCount: 0, drawCount: 0 });
expect(users).toEqual({});
expect(dateVotes[gameId]).toEqual({ team: "LG" });
});
});
});