fix(runtime): stage runtime installs strictly, time out only on stalls, and make reinstall actually reinstall

This commit is contained in:
Yun Chan 2026-09-28 00:53:44 +09:00
parent ba0dbbd813
commit 524d390bc2
10 changed files with 1160 additions and 238 deletions

View file

@ -0,0 +1,62 @@
// tests/unit/stt-tab-subscriptions-redteam-r1-8.test.ts
// STT 탭 언마운트 시 모든 진행률 구독이 해제되는지 (runtime:progress 리스너 누수 회귀).
import { describe, it, expect } from 'vitest'
import { readFileSync } from 'node:fs'
import { join } from 'node:path'
import { subscribeSttTabEvents } from '../../src/renderer/components/stt/sttTabSubscriptions'
type Listener = (event: number) => void
function fakeChannel(): { subscribe: (cb: Listener) => () => void; listeners: Set<Listener> } {
const listeners = new Set<Listener>()
return {
listeners,
subscribe: (cb) => {
listeners.add(cb)
return () => {
listeners.delete(cb)
}
},
}
}
describe('subscribeSttTabEvents', () => {
it('마운트·언마운트를 반복해도 리스너가 쌓이지 않는다', () => {
const download = fakeChannel()
const runtime = fakeChannel()
for (let mount = 0; mount < 12; mount += 1) {
const cleanup = subscribeSttTabEvents(
{ onDownloadProgress: download.subscribe, onRuntimeProgress: runtime.subscribe },
{ onDownloadProgress: () => undefined, onRuntimeProgress: () => undefined },
)
expect(download.listeners.size).toBe(1)
expect(runtime.listeners.size).toBe(1)
cleanup()
}
expect(download.listeners.size).toBe(0)
expect(runtime.listeners.size).toBe(0)
})
it('cleanup 을 두 번 불러도 안전하다 (StrictMode 이중 호출)', () => {
const download = fakeChannel()
const runtime = fakeChannel()
const cleanup = subscribeSttTabEvents(
{ onDownloadProgress: download.subscribe, onRuntimeProgress: runtime.subscribe },
{ onDownloadProgress: () => undefined, onRuntimeProgress: () => undefined },
)
cleanup()
expect(() => cleanup()).not.toThrow()
})
it('STTTab 은 진행률 이벤트를 이 헬퍼로만 구독한다 (개별 구독 + 누락된 해제 재발 방지)', () => {
const source = readFileSync(
join(__dirname, '../../src/renderer/components/STTTab.tsx'),
'utf8',
)
expect(source).toContain('return subscribeSttTabEvents(')
expect(source).not.toMatch(/const\s+unsub\w*\s*=\s*window\.electronAPI\.(stt|runtime)\.on/)
})
})