import { createServer } from 'vite'; const server = await createServer({ logLevel: 'error', server: { middlewareMode: true, hmr: false }, appType: 'custom' }); try { const { defaultLongWaitBattleThreshold, defaultRecentRecruitBattleWindow, deriveSortieRosterHistory, deriveSortieRosterHistoryByUnitId } = await server.ssrLoadModule('/src/game/data/sortieRosterHistory.ts'); assert(defaultRecentRecruitBattleWindow === 1, 'Recent-recruit window must retain its one-battle default.'); assert(defaultLongWaitBattleThreshold === 3, 'Long-wait threshold must retain its three-battle default.'); const empty = deriveSortieRosterHistory(['liu-bei'], {}); assert(empty.length === 1, `Empty history must still return the requested roster unit: ${JSON.stringify(empty)}`); assert( empty[0].sortieCount === 0 && empty[0].firstSortiePending === true && empty[0].battlesSinceLastSortie === null && empty[0].recentRecruit === false && empty[0].badge.kind === 'first-sortie' && empty[0].badge.label === '미출전', `Empty history must produce a stable first-sortie state: ${JSON.stringify(empty[0])}` ); const firstSortieHistory = { opening: battle('opening', 1, ['guan-yu', 'guan-yu']) }; const frozenFirstSortieHistory = JSON.stringify(firstSortieHistory); const firstSortie = deriveSortieRosterHistory(['guan-yu'], firstSortieHistory)[0]; assert( firstSortie.sortieCount === 1 && firstSortie.firstSortiePending === false && firstSortie.battlesSinceLastSortie === 0 && firstSortie.lastSortieBattleId === 'opening' && firstSortie.badge.kind === 'active' && firstSortie.badge.label === '출전 1회', `A first completed sortie must become the active state exactly once: ${JSON.stringify(firstSortie)}` ); assert( JSON.stringify(firstSortieHistory) === frozenFirstSortieHistory, 'History derivation must not mutate campaign battle-history-like input.' ); const longWait = deriveSortieRosterHistory(['zhang-fei'], { fourth: battle('fourth', 4), first: battle('first', 1, ['zhang-fei']), third: battle('third', 3), second: battle('second', 2) })[0]; assert( longWait.sortieCount === 1 && longWait.battlesSinceLastSortie === 3 && longWait.lastSortieBattleId === 'first' && longWait.badge.kind === 'long-wait' && longWait.badge.label === '대기 3전', `Three later battles must produce a long-wait badge even when record insertion is unordered: ${JSON.stringify(longWait)}` ); const recentRecruit = deriveSortieRosterHistory(['jian-yong'], { recruitment: battle('recruitment', 1, [], ['jian-yong']) })[0]; assert( recentRecruit.sortieCount === 0 && recentRecruit.firstSortiePending === true && recentRecruit.recentRecruit === true && recentRecruit.battlesSinceRecruit === 0 && recentRecruit.recruitBattleId === 'recruitment' && recentRecruit.badge.kind === 'recent-recruit' && recentRecruit.badge.label === 'NEW' && recentRecruit.summary === '최근 합류 · 첫 출전 대기', `A latest-battle recruit must expose the concise NEW state: ${JSON.stringify(recentRecruit)}` ); const experiencedRecentRecruit = deriveSortieRosterHistory(['guan-yu'], { recruitment: battle('recruitment', 1, ['guan-yu'], ['guan-yu']) })[0]; assert( experiencedRecentRecruit.sortieCount === 1 && experiencedRecentRecruit.firstSortiePending === false && experiencedRecentRecruit.recentRecruit === true && experiencedRecentRecruit.badge.kind === 'recent-recruit' && experiencedRecentRecruit.badge.label === 'NEW', `A recruit who fought before formally joining must retain the NEW roster badge: ${JSON.stringify(experiencedRecentRecruit)}` ); const byUnitId = deriveSortieRosterHistoryByUnitId( ['jian-yong', 'liu-bei', 'jian-yong', ' '], [battle('recruitment', 1, ['liu-bei'], ['jian-yong'])] ); assert( byUnitId.size === 2 && byUnitId.get('jian-yong')?.recentRecruit === true && byUnitId.get('liu-bei')?.sortieCount === 1, `Map derivation must preserve unique roster entries and their states: ${JSON.stringify([...byUnitId])}` ); console.log('Verified sortie roster history: empty, first sortie, long wait, recent recruit, ordering, and immutability.'); } finally { await server.close(); } function battle(battleId, day, selectedUnitIds = [], recruitUnitIds = []) { return { battleId, completedAt: new Date(Date.UTC(2026, 0, day)).toISOString(), sortiePerformance: { selectedUnitIds }, campaignRewards: { recruits: recruitUnitIds.map((unitId) => ({ unitId })) } }; } function assert(condition, message) { if (!condition) { throw new Error(message); } }