import { afterAll, beforeAll, expect, it } from 'vitest'; import { getFirestore, Timestamp } from 'firebase-admin/firestore'; import { eclipseTarget } from '../src/fandom/eclipse_target.js'; import { bootAdmin, shutdownAdmin } from './support.js'; beforeAll(bootAdmin); afterAll(async () => { await getFirestore().recursiveDelete(getFirestore().doc('fandoms/test-eclipse-target')); await shutdownAdmin(); }); it('생성일·0명·누락·최근 7일·목표율을 구분해 고유 참여자 목표를 산출한다', async () => { const ref = getFirestore().doc('fandoms/test-eclipse-target'); const created = (date: string) => Timestamp.fromDate(new Date(`${date}T00:00:00+09:00`)); const stats = ref.collection('fandomDailyStats'); expect(await eclipseTarget(ref, created('2026-09-22'), '2026-09-22')).toEqual({ target: 1, averageDau: null, dauDays: [], }); expect(await eclipseTarget(ref, undefined, '2026-09-22')).toMatchObject({ result: 'cancelled_error' }); expect(await eclipseTarget(ref, created('2026-09-23'), '2026-09-22')).toMatchObject({ result: 'cancelled_error' }); expect(await eclipseTarget(ref, created('2026-09-21'), '2026-09-22')).toMatchObject({ result: 'cancelled_error' }); await stats.doc('2026-09-21').set({ dau: 0, active30: 10000 }); expect(await eclipseTarget(ref, created('2026-09-21'), '2026-09-22')).toMatchObject({ target: 1, averageDau: 0 }); await stats.doc('2026-09-20').set({ dau: 30 }); await stats.doc('2026-09-19').set({ dau: 60 }); expect(await eclipseTarget(ref, created('2026-09-19'), '2026-09-22')).toEqual({ target: 2, averageDau: 30, dauDays: ['2026-09-21', '2026-09-20', '2026-09-19'], }); expect(await eclipseTarget(ref, created('2026-09-19'), '2026-09-22', 0.1)).toMatchObject({ target: 3 }); for (const rate of [0, -1, Number.NaN, Number.POSITIVE_INFINITY, 1.1]) { expect(await eclipseTarget(ref, created('2026-09-19'), '2026-09-22', rate)).toMatchObject({ result: 'cancelled_error' }); } for (let d = 15; d <= 21; d++) await stats.doc(`2026-09-${d}`).set({ dau: 30 }); await stats.doc('2026-09-14').set({ dau: 100000 }); await stats.doc('2026-09-22').set({ dau: 100000 }); expect(await eclipseTarget(ref, created('2026-09-01'), '2026-09-22')).toMatchObject({ target: 2, averageDau: 30 }); // C-21 집계 기간은 운영값 — 8일로 늘리면 14일의 값이 분모에 들어온다. expect(await eclipseTarget(ref, created('2026-09-01'), '2026-09-22', 0.05, 8)).toMatchObject({ target: Math.ceil((30 * 7 + 100000) / 8 * 0.05), averageDau: (30 * 7 + 100000) / 8, }); expect(await eclipseTarget(ref, created('2026-09-01'), '2026-09-22', 0.05, 0)).toMatchObject({ result: 'cancelled_error' }); for (const invalid of [-1, 0.5, '30', null]) { await stats.doc('2026-09-20').set({ dau: invalid }); expect(await eclipseTarget(ref, created('2026-09-01'), '2026-09-22')).toMatchObject({ result: 'cancelled_error' }); } await stats.doc('2026-09-20').delete(); expect(await eclipseTarget(ref, created('2026-09-01'), '2026-09-22')).toMatchObject({ result: 'cancelled_error' }); });