// tests/unit/renderer-models-redteam-r2-1.test.ts // 렌더러 순수 모델: // - 녹음 화면 복원: main 사본과 먼저 도착한 이벤트를 id 로 합친다 // - 회의 채팅 패널: main 이 기억하는 대화를 표시 메시지로 바꾼다(키 충돌 없음) // - 명령어 폼: 저장 거부 사유를 검증 메시지로 바꾼다(빈 프롬프트·길이 초과) import { describe, expect, it } from 'vitest' import { ErrorCode } from '@d3ro/core/errors' import { mergeRestoredItems } from '../../src/renderer/pages/meeting/live-restore' import { toPanelMessages, withRestoredHistory } from '../../src/renderer/components/meeting/meeting-chat-history' import { commandSaveError } from '../../src/renderer/pages/commands/command-form' describe('mergeRestoredItems', () => { it('main 사본을 기준으로, 조회 중 먼저 온 항목은 중복 없이 합치고 시간 순으로 둔다', () => { const restored = [ { id: 'a', t: 1 }, { id: 'b', t: 2 }, ] const arrived = [ { id: 'b', t: 2 }, { id: 'c', t: 3 }, ] expect(mergeRestoredItems(restored, arrived, (x) => x.t).map((x) => x.id)).toEqual(['a', 'b', 'c']) }) }) describe('meeting chat history', () => { it('보관된 턴을 순서 번호 timestamp 로 바꿔 목록 키가 겹치지 않는다', () => { const messages = toPanelMessages([ { role: 'user', content: 'q1' }, { role: 'assistant', content: 'a1' }, { role: 'user', content: 'q2' }, ]) const keys = messages.map((m) => `${m.role}-${m.timestamp}`) expect(new Set(keys).size).toBe(3) expect(messages.map((m) => m.content)).toEqual(['q1', 'a1', 'q2']) }) it('조회가 끝나기 전에 보낸 새 메시지는 지우지 않고 뒤에 둔다', () => { const merged = withRestoredHistory( [{ role: 'user', content: '새 질문', timestamp: 1_700_000_000_000 }], [{ role: 'user', content: '이전 질문' }, { role: 'assistant', content: '이전 답' }], ) expect(merged.map((m) => m.content)).toEqual(['이전 질문', '이전 답', '새 질문']) }) }) describe('commandSaveError', () => { it('빈 프롬프트·길이 초과는 저장 전에 검증 메시지다', () => { expect(commandSaveError({ kind: 'draft', draft: { name: 'n', description: '', prompt: ' ' } })).toEqual({ key: 'mobile.commands.syncError.validation', }) expect(commandSaveError({ kind: 'draft', draft: { name: 'n'.repeat(81), description: '', prompt: 'p' } })).not.toBeNull() expect(commandSaveError({ kind: 'draft', draft: { name: 'n', description: '', prompt: 'p' } })).toBeNull() }) it('main 의 검증 거부는 검증 메시지, 그 밖의 실패는 호출자가 원문을 보여 준다(null)', () => { expect( commandSaveError({ kind: 'ipc', error: { code: ErrorCode.ConfigWriteFailed, message: 'Failed: Instruction prompt is too long (max 4000 characters)' } }), ).not.toBeNull() expect(commandSaveError({ kind: 'ipc', error: { code: ErrorCode.ConfigInvalidValue, message: 'x' } })).not.toBeNull() expect(commandSaveError({ kind: 'ipc', error: { code: ErrorCode.ConfigWriteFailed, message: 'disk full' } })).toBeNull() }) })