알림함 항목 생성 · 알림 이동 · 브리핑 항목 승인·상세 · 푸시 토픽을 가입 팬덤 전체로
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { afterAll, beforeAll, expect, it } from 'vitest';
|
||||
import { getFirestore } from 'firebase-admin/firestore';
|
||||
|
||||
import { notifyEclipseTransition, notifyHeroDrop, notifyQuestDone } from '../src/notify/inbox.js';
|
||||
import { bootAdmin, shutdownAdmin } from './support.js';
|
||||
|
||||
/** 알림함 항목 — Figma `229:11265` 표본 문구 · 명세 `518:88353` 이동 규칙의 서버 절반. */
|
||||
|
||||
const FANDOM = 'test-inbox-fandom';
|
||||
const A = 'test-inbox-a';
|
||||
const B = 'test-inbox-b';
|
||||
|
||||
async function inbox(uid: string) {
|
||||
const snap = await getFirestore().collection(`users/${uid}/notifications`).get();
|
||||
return snap.docs.map((d) => d.data());
|
||||
}
|
||||
|
||||
async function wipe() {
|
||||
const db = getFirestore();
|
||||
for (const uid of [A, B]) await db.recursiveDelete(db.doc(`users/${uid}`));
|
||||
await db.recursiveDelete(db.doc(`fandoms/${FANDOM}`));
|
||||
await db.doc('quests/test-inbox-quest').delete();
|
||||
}
|
||||
|
||||
beforeAll(async () => {
|
||||
bootAdmin();
|
||||
await wipe();
|
||||
const db = getFirestore();
|
||||
await db.doc(`fandoms/${FANDOM}`).set({ name: 'BTS' });
|
||||
for (const uid of [A, B]) await db.doc(`fandoms/${FANDOM}/members/${uid}`).set({ joinedAt: new Date() });
|
||||
await db.doc('quests/test-inbox-quest').set({ title: '만보기' });
|
||||
});
|
||||
afterAll(async () => { await wipe(); await shutdownAdmin(); });
|
||||
|
||||
it('이클립스 발생 · 방어는 회원 전원에게 한 건씩, 재전달해도 늘지 않는다', async () => {
|
||||
expect(await notifyEclipseTransition(FANDOM, 'r1', { status: 'scheduled' }, { status: 'active' })).toBe('started');
|
||||
expect(await notifyEclipseTransition(FANDOM, 'r1', { status: 'scheduled' }, { status: 'active' })).toBe('started');
|
||||
expect(await notifyEclipseTransition(FANDOM, 'r1', { status: 'active' }, { status: 'ended', result: 'success' })).toBe('defended');
|
||||
// 실패 종료 · 상태 변화 없음은 알리지 않는다.
|
||||
expect(await notifyEclipseTransition(FANDOM, 'r2', { status: 'active' }, { status: 'ended', result: 'failed' })).toBeNull();
|
||||
expect(await notifyEclipseTransition(FANDOM, 'r1', { status: 'active' }, { status: 'active' })).toBeNull();
|
||||
|
||||
for (const uid of [A, B]) {
|
||||
const items = await inbox(uid);
|
||||
const titles = items.map((n) => n.title).sort();
|
||||
expect(titles).toEqual(['내 행성의 이클립스가 발생했어요.', '내 행성의 이클립스를 방어했어요.']);
|
||||
expect(items.every((n) => n.eventType === 'eclipse' && n.source === 'BTS' && !n.targetPath)).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('영웅의 선물 발동은 회원 전원에게 발동자 닉네임으로', async () => {
|
||||
await notifyHeroDrop(FANDOM, 'd1', '팜하니짱');
|
||||
const hero = (await inbox(B)).filter((n) => n.eventType === 'item_used');
|
||||
expect(hero).toHaveLength(1);
|
||||
expect(hero[0]!.title).toBe('팜하니짱님의 고귀한 기여로 영웅의 선물이 발동되었습니다!');
|
||||
expect(hero[0]!.source).toBe('아이템');
|
||||
});
|
||||
|
||||
it('퀘스트 완료는 횟수가 오른 때만 본인에게, 상세 경로를 싣는다', async () => {
|
||||
const day = '2026-10-05';
|
||||
expect(await notifyQuestDone(A, 'test-inbox-quest', undefined, { dayKey: day, count: 1 })).toBe(true);
|
||||
// 같은 횟수 재기록(updatedAt 만 바뀜)은 알리지 않는다.
|
||||
expect(await notifyQuestDone(A, 'test-inbox-quest', { dayKey: day, count: 1 }, { dayKey: day, count: 1 })).toBe(false);
|
||||
expect(await notifyQuestDone(A, 'test-inbox-quest', { dayKey: day, count: 1 }, { dayKey: day, count: 2 })).toBe(true);
|
||||
const quest = (await inbox(A)).filter((n) => n.eventType === 'quest');
|
||||
expect(quest).toHaveLength(2);
|
||||
expect(quest[0]!.title).toBe('[만보기] 퀘스트를 완료했어요!');
|
||||
expect(quest[0]!.targetPath).toBe('quests/test-inbox-quest');
|
||||
});
|
||||
Reference in New Issue
Block a user