d3ro-voice/apps/desktop/tests/main/services/keybinding-altgr-redteam-r3-8.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

104 lines
3.6 KiB
TypeScript

// tests/main/services/keybinding-altgr-redteam-r3-8.test.ts
// 레드팀 r3-8 회귀 테스트 (데스크톱 어댑터) — AltGr 배열.
//
// Windows 는 AltGr 를 "가짜 LCtrl 눌림 → RAlt 눌림"(같은 time)으로 보내고, libuiohook 은 그
// RAlt 눌림에 ctrlKey 를 싣는다. 어댑터가 그대로 옮기면 '@'(AltGr+Q) 마다 'command' 가 발동하고
// 기본 dictation(RightAlt 단독)은 발동하지 않는다.
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import { uIOhook, UiohookKey } from 'uiohook-napi'
import type { UiohookKeyboardEvent } from 'uiohook-napi'
import { ALTGR_CHORD_GRACE_MS } from '@d3ro/core/keybinding-runtime'
import { initInMemoryConfig, resetInMemoryConfig } from '../../../src/main/services/ConfigService'
import { getKeyBindingService } from '../../../src/main/services/KeyBindingService'
import type { KeyBindingTriggerPayload } from '../../../src/main/services/KeyBindingService'
const originalPlatform = Object.getOwnPropertyDescriptor(process, 'platform')
function setPlatform(platform: NodeJS.Platform): void {
Object.defineProperty(process, 'platform', { value: platform, configurable: true })
}
function restorePlatform(): void {
if (originalPlatform) Object.defineProperty(process, 'platform', originalPlatform)
}
type KeyHandler = (e: UiohookKeyboardEvent) => void
function handlerOf(event: 'keydown' | 'keyup'): KeyHandler {
const calls = vi.mocked(uIOhook.on).mock.calls.filter((c) => c[0] === event)
const last = calls[calls.length - 1]
if (!last) throw new Error(`no ${event} handler`)
return last[1] as KeyHandler
}
interface Mods {
ctrl?: boolean
alt?: boolean
}
function keyEvent(keycode: number, time: number, mods: Mods = {}): UiohookKeyboardEvent {
return {
keycode,
time,
ctrlKey: mods.ctrl ?? false,
altKey: mods.alt ?? false,
shiftKey: false,
metaKey: false
} as unknown as UiohookKeyboardEvent
}
describe('KeyBindingService — AltGr 배열 (Windows)', () => {
const events: string[] = []
const listener = (p: KeyBindingTriggerPayload): void => {
events.push(`${p.actionId}:${p.type}`)
}
beforeEach(() => {
vi.useFakeTimers()
setPlatform('win32')
initInMemoryConfig()
vi.mocked(uIOhook.on).mockClear()
events.length = 0
const service = getKeyBindingService()
service.loadFromConfig()
service.start()
service.on('triggered', listener)
})
afterEach(() => {
const service = getKeyBindingService()
service.off('triggered', listener)
service.stop()
resetInMemoryConfig()
restorePlatform()
vi.useRealTimers()
})
function altGrDown(time: number): void {
handlerOf('keydown')(keyEvent(UiohookKey.Ctrl, time, { ctrl: true }))
handlerOf('keydown')(keyEvent(UiohookKey.AltRight, time, { ctrl: true, alt: true }))
}
function altGrUp(): void {
handlerOf('keyup')(keyEvent(UiohookKey.Ctrl, 0, { alt: true }))
handlerOf('keyup')(keyEvent(UiohookKey.AltRight, 0))
}
it("AltGr+Q('@') 는 어떤 트리거도 내보내지 않는다", () => {
altGrDown(5_000)
handlerOf('keydown')(keyEvent(UiohookKey.Q, 5_040, { ctrl: true, alt: true }))
handlerOf('keyup')(keyEvent(UiohookKey.Q, 5_080, { ctrl: true, alt: true }))
altGrUp()
vi.advanceTimersByTime(1_000)
expect(events).toEqual([])
})
it('AltGr 를 누르고 있으면 기본 dictation 이 발동한다 (command 가 아니다)', () => {
altGrDown(6_000)
vi.advanceTimersByTime(ALTGR_CHORD_GRACE_MS)
expect(events).toEqual(['dictation:pressed'])
altGrUp()
expect(events).toEqual(['dictation:pressed', 'dictation:released'])
})
})