d3ro-voice/packages/core/__tests__/template-field-policy.test.ts
Yun Chan ba9ef9741e fix: red-team round 3 hardening across desktop, mobile, core and server
Batch of red-team r3 fixes that were in the working tree before the
2026-09-28 design overhaul, committed as one unit with their tests.

- desktop main: STT timeouts and sidecar, voice recording store, sync
  (credentials, audio, knowledge reindex, push gates), runtime
  provisioner, update policy, AltGr keybindings, voice-command policy,
  dictionary file codec/limits, meeting transcript condensing and a
  local recording ledger so interrupted-session recovery only closes
  meetings this device recorded (a phone's live meeting is left alone).
- mobile: login CSRF via implicit token callbacks rejected, account
  deletion/retention, durable queue retention, knowledge realtime
  without unfiltered DELETE, meeting re-record failure paths, cloud STT
  client, preferences store/resync.
- core: text chunking splits long unbroken transcripts to fit, template
  field policy, dictionary limits, meeting markdown inline handling.
- server: payple webhook policy and cancellation order scope, meeting
  document generation quota, team RPC null-role guard, unified LLM
  quota in-flight accounting, knowledge chunk vector index, meeting
  re-record failure paths (migrations 20260929*).
- ci: portable/runtime feed gates, update-policy schema, Forgejo file
  delete and alias planning.

Four older tests are updated to the new contracts rather than the old
behavior: token-pair auth callbacks are rejected, knowledge realtime no
longer subscribes to DELETE, long transcript lines are split, and
meeting recovery requires the local recording ledger for empty rows.
2026-09-28 20:45:52 +09:00

59 lines
2.3 KiB
TypeScript

import { describe, expect, it } from 'vitest'
import {
findTemplateFieldIdProblem,
nextTemplateFieldId,
nextTemplateFieldNumber,
templateFieldIdProblems,
} from '../src/template-field-policy'
const ids = (...values: string[]) => values.map((id) => ({ id }))
describe('templateFieldIdProblems', () => {
it('유효한 id는 문제가 없다', () => {
expect(templateFieldIdProblems(ids('a', 'b', 'c'))).toEqual([null, null, null])
expect(findTemplateFieldIdProblem(ids('a', 'b'))).toBeNull()
})
it('빈 id와 공백뿐인 id는 empty', () => {
expect(templateFieldIdProblems(ids('a', '', ' '))).toEqual([null, 'empty', 'empty'])
})
it('겹치는 id는 모든 행이 duplicate', () => {
expect(templateFieldIdProblems(ids('field3', 'x', 'field3'))).toEqual(['duplicate', null, 'duplicate'])
})
it('id는 정확히 비교한다(자리표시자 키가 정확 일치이므로)', () => {
expect(templateFieldIdProblems(ids('a', ' a', 'A'))).toEqual([null, null, null])
})
it('findTemplateFieldIdProblem은 첫 문제의 위치와 id를 돌려준다', () => {
expect(findTemplateFieldIdProblem(ids('a', 'b', 'b'))).toEqual({ index: 1, id: 'b', problem: 'duplicate' })
expect(findTemplateFieldIdProblem(ids('a', ''))).toEqual({ index: 1, id: '', problem: 'empty' })
})
})
describe('nextTemplateFieldId', () => {
it('빈 목록이면 field1', () => {
expect(nextTemplateFieldId([])).toBe('field1')
})
it('중간 필드를 지운 뒤에도 기존 id를 다시 만들지 않는다 (회귀: 개수 기반 번호)', () => {
// [field1, field2, field3]에서 field1을 지움 → 개수+1 = field3 (중복)
expect(nextTemplateFieldId(ids('field2', 'field3'))).toBe('field4')
})
it('사용자가 이름을 바꾼 필드도 번호 계산에 넣는다', () => {
expect(nextTemplateFieldNumber(ids('recipient', 'field7', 'body'))).toBe(8)
expect(nextTemplateFieldId(ids('recipient', 'body'))).toBe('field1')
})
it('추가·삭제를 섞어 반복해도 id가 겹치지 않는다', () => {
const fields = ids('field1')
for (let i = 0; i < 6; i += 1) {
fields.push({ id: nextTemplateFieldId(fields) })
fields.push({ id: nextTemplateFieldId(fields) })
fields.splice(i % fields.length, 1)
expect(findTemplateFieldIdProblem(fields)).toBeNull()
}
})
})