import test from 'node:test'; import assert from 'node:assert/strict'; import { compactResult, currentGrowthSettings, snapshotKey, type AnalysisSettings, type GrowthSnapshot } from '../models/analysis-history.ts'; import { parseAnalysisArchive } from '../storage/analysis-history.ts'; import type { ExactOptimizationResult } from './types.ts'; const growth: GrowthSnapshot = { collection: { a: { owned: true, awakening: 2 } }, memberBoards: { a: ['B1', 'G1'] }, boardCards: {}, memoryCount: 10, memberPowerUpPercent: 1 }; test('동일한 성장 상태는 입력 순서와 무관하게 공유하고 성장 차이는 구분한다', () => { const reordered = { ...growth, memberBoards: { a: ['G1', 'B1'] } }; assert.equal(snapshotKey(growth), snapshotKey(reordered)); assert.notEqual(snapshotKey(growth), snapshotKey({ ...growth, memoryCount: 11 })); }); test('현재 성장 비교는 저장 조건을 유지하고 보유 개화만 교체한다', () => { const settings: AnalysisSettings = { songId: 'song', difficulty: 'expert', cardIds: ['a'], awakenings: [0], allBlueNodes: [true], generationBonusOverrides: [900], leaderCharacterId: 'a', leaderOutfitCardId: 'a' }; const current = currentGrowthSettings(settings, growth); assert.deepEqual(current.awakenings, [2]); assert.deepEqual(settings.awakenings, [0]); assert.deepEqual(current.generationBonusOverrides, [900]); assert.deepEqual(current.allBlueNodes, [true]); assert.throws(() => currentGrowthSettings(settings, { ...growth, collection: {} }), /보유/); }); test('기록은 노트별 배열을 제외하고 원본과 독립적으로 보존한다', () => { const source = { notes: [{ timeSeconds: 1 }], scoreBySecond: [2], order: ['a'], expectedScore: 123 } as unknown as ExactOptimizationResult; const saved = compactResult(source); source.order = ['b']; assert.deepEqual(saved.order, ['a']); assert.equal(saved.expectedScore, 123); assert.equal('notes' in saved, false); assert.equal('scoreBySecond' in saved, false); }); test('기록 백업은 잘못된 구조와 누락된 성장 참조를 거부한다', () => { assert.throws(() => parseAnalysisArchive('{}')); assert.throws(() => parseAnalysisArchive(JSON.stringify({ schemaVersion: 1, snapshots: [], records: [{ settings: {}, result: {}, snapshotId: 'missing' }] }))); assert.deepEqual(parseAnalysisArchive('{"schemaVersion":1,"snapshots":[],"records":[]}').records, []); }); test('성장 상태를 공유하는 기록 백업을 손실 없이 읽고 잘못된 개화를 거부한다', () => { const ids = ['a', 'b', 'c', 'd', 'e']; const record = { id: 'record', name: '기록', title: '곡', analyzedAt: '2026-09-06T00:00:00Z', snapshotId: 'snapshot', overallPower: 1000, durationSeconds: 120, settings: { songId: 'song', difficulty: 'expert', cardIds: ids, awakenings: [0, 1, 2, 3, 4], leaderCharacterId: 'a', leaderOutfitCardId: 'a' }, result: { expectedScore: 100, theoreticalMaxScore: 120, baselineScore: 50, perfectNoteScore: 10, order: ids, cooldownReductions: Object.fromEntries(ids.map((id) => [id, 0])) } }; const archive = { schemaVersion: 1, snapshots: [{ id: 'snapshot', growth }], records: [record, { ...record, id: 'second' }] }; const parsed = parseAnalysisArchive(JSON.stringify(archive)); assert.equal(parsed.records.length, 2); assert.equal(parsed.snapshots.length, 1); assert.deepEqual(parsed.snapshots[0].growth, growth); for (const difficulty of ['easy', 'normal', 'hard', 'expert']) { record.settings.difficulty = difficulty; assert.equal(parseAnalysisArchive(JSON.stringify(archive)).records[0].settings.difficulty, difficulty); } record.settings.difficulty = 'invalid'; assert.throws(() => parseAnalysisArchive(JSON.stringify(archive))); record.settings.difficulty = 'expert'; record.settings.awakenings[0] = 8; assert.throws(() => parseAnalysisArchive(JSON.stringify(archive))); });