import { afterAll, beforeAll, expect, it, vi } from 'vitest'; import { DocumentReference, getFirestore } from 'firebase-admin/firestore'; import { fnPlanetEvolutionRecord } from '../src/fandom/evolution.js'; import { bootAdmin, shutdownAdmin } from './support.js'; const id = 'test-evolution-history'; beforeAll(() => { bootAdmin(); }); afterAll(async () => { vi.restoreAllMocks(); await getFirestore().recursiveDelete(getFirestore().doc(`fandoms/${id}`)); await shutdownAdmin(); }); async function event(beforeStage: unknown, afterStage: unknown, eventId: string) { const db = getFirestore(); const ref = db.doc(`fandoms/${id}`); await ref.set({ ...(beforeStage === undefined ? {} : { stage: beforeStage }) }); const before = (await db.collection('fandoms').where('__name__', '==', id).get()).docs[0]!; await ref.set({ stage: afterStage }); const after = (await db.collection('fandoms').where('__name__', '==', id).get()).docs[0]!; return { specversion: '1.0' as const, id: eventId, source: 'firestore', type: 'google.cloud.firestore.document.v1.updated', time: new Date().toISOString(), data: { before, after }, params: { fandomId: id }, location: 'asia-northeast3', project: 'demo-fanuzu', database: '(default)', namespace: '', document: ref.path, }; } it('저장 실패를 재시도하고 동시 재전달에도 확정 진화를 한 번만 남긴다', async () => { const input = await event(1, 3, 'confirmed/event'); const create = vi.spyOn(DocumentReference.prototype, 'create').mockRejectedValueOnce(new Error('write failed')); try { await expect(fnPlanetEvolutionRecord.run(input)).rejects.toThrow('write failed'); } finally { create.mockRestore(); } await Promise.all([fnPlanetEvolutionRecord.run(input), fnPlanetEvolutionRecord.run(input)]); const records = await getFirestore().collection(`fandoms/${id}/stateHistory`).get(); expect(records.size).toBe(1); const record = records.docs[0]!; expect(record.get('kind')).toBe('evolution'); expect(record.get('fromStage')).toBe(1); expect(record.get('toStage')).toBe(3); expect(record.get('occurredAt').isEqual(input.data.after.updateTime)).toBe(true); expect(record.get('participantIds')).toBeUndefined(); }); it('동일 Stage·하락·누락·범위 밖 수치를 진화로 기록하지 않는다', async () => { const history = getFirestore().collection(`fandoms/${id}/stateHistory`); const count = (await history.get()).size; for (const [before, after] of [[3, 3], [3, 2], [undefined, 3], [-1, 1], [6, 7], [1, '2']]) { await fnPlanetEvolutionRecord.run(await event(before, after, `skip-${before}-${after}`)); } expect((await history.get()).size).toBe(count); });