d3ro-voice/apps/desktop/tests/main/utils/paths.test.ts
Yun Chan 0d92a4a853
Some checks failed
ci / 정본·보안·린트·타입·테스트 (push) Successful in 51s
ci / 모바일 린트·타입·Jest (push) Successful in 37s
ci / Supabase Edge Functions + Cloudflare Worker (push) Successful in 26s
ci / .NET API 서버 테스트 (push) Successful in 14s
deploy-site / deploy (push) Failing after 15s
ci / 워크스페이스 빌드 검증 (push) Failing after 11m7s
fix(rag): do not mark a document indexed when no chunk could be embedded
With the embedding server unavailable every chunk failed, yet the document
was stored as indexed=true with 0 chunks, so the knowledge base listed it as
searchable while queries could never match it. The red use-case test caught
this; it had been written off as an environment failure.

Now a run with zero embedded chunks leaves indexed=false and throws
RAGEmbeddingFailed (surfaced by reindex, logged by addDocument).

Tests that only hold on the Windows developer machine now declare it: the
bundled SoX binary and PowerShell device discovery run on win32 only, and
the sidecar venv test runs only when sidecar/.venv exists. The Linux Forgejo
runner skips them instead of failing.
2026-09-26 21:10:40 +09:00

99 lines
3.5 KiB
TypeScript

// tests/main/utils/paths.test.ts
// dev/packaged 경로 해석 테스트.
//
// 회귀 배경: electron-vite dev에서 app.getAppPath()가 `out/main`을 가리켜
// sidecar/SoX가 존재하지 않는 경로로 잡혔고, 결과적으로 로컬 전사와 녹음이
// 모두 실패했다(시스템 python/PATH 폴백). 여기서 그 해석을 고정한다.
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
import { existsSync } from 'node:fs'
import path from 'node:path'
import { app } from 'electron'
import {
getAppRoot,
getSidecarCommand,
getSidecarBaseUrl,
getSoxPath,
resetPathCache,
} from '../../../src/main/utils/paths'
const desktopDir = path.resolve(__dirname, '..', '..', '..')
describe('paths (dev)', () => {
beforeEach(() => {
resetPathCache()
})
afterEach(() => {
vi.restoreAllMocks()
resetPathCache()
})
it('resolves the app root a level above the vite out/main bundle', () => {
// dev에서 app.getAppPath() = <desktop>/out/main 이다.
vi.mocked(app.getAppPath).mockReturnValue(path.join(desktopDir, 'out', 'main'))
resetPathCache()
expect(getAppRoot()).toBe(desktopDir)
})
// resources/sox 에는 Windows용 sox.exe 만 커밋돼 있다(앱은 Windows 배포). 다른 OS에서는 PATH 의 sox 를 쓴다.
it.skipIf(process.platform !== 'win32')('prefers the bundled SoX binary over the system PATH', () => {
expect(getSoxPath()).toContain(path.join('resources', 'sox', 'sox'))
expect(existsSync(getSoxPath())).toBe(true)
expect(getSoxPath()).not.toBe('sox')
})
// 로컬 개발 환경에만 있는 sidecar/.venv 가 있을 때의 동작이다. CI 에는 venv 가 없다.
it.skipIf(!existsSync(path.join(desktopDir, 'sidecar', '.venv')))('uses the sidecar virtualenv python when it exists', () => {
const launch = getSidecarCommand()
expect(launch.source).toBe('venv')
expect(launch.command).toContain('.venv')
expect(launch.args[0]).toBe(path.join(desktopDir, 'sidecar', 'main.py'))
expect(existsSync(launch.command)).toBe(true)
})
it('builds the sidecar base URL on the IPv4 loopback', () => {
expect(getSidecarBaseUrl(18765)).toBe('http://127.0.0.1:18765')
})
})
describe('paths (packaged)', () => {
beforeEach(() => {
resetPathCache()
Object.defineProperty(app, 'isPackaged', { value: true, configurable: true })
})
afterEach(() => {
Object.defineProperty(app, 'isPackaged', { value: false, configurable: true })
vi.restoreAllMocks()
resetPathCache()
})
it('fails loudly when the packaged sidecar bundle is missing', () => {
Object.defineProperty(process, 'resourcesPath', {
value: path.join(desktopDir, 'definitely-not-bundled'),
configurable: true,
})
expect(() => getSidecarCommand()).toThrowError(/로컬 음성 엔진이 아직 설치되지 않았습니다/)
})
it('uses the packaged sidecar executable when present', () => {
Object.defineProperty(process, 'resourcesPath', {
value: path.join(desktopDir, 'resources'),
configurable: true,
})
// resources/sox 는 커밋되어 있으므로 resources/sidecar/sidecar(.exe)도
// 같은 방식으로 배치된다. 존재하지 않는 플랫폼이면 번들 실패로 처리된다.
const exeSuffix = process.platform === 'win32' ? '.exe' : ''
const bundled = path.join(desktopDir, 'resources', 'sidecar', `sidecar${exeSuffix}`)
if (existsSync(bundled)) {
expect(getSidecarCommand().source).toBe('bundled')
} else {
expect(() => getSidecarCommand()).toThrow()
}
})
})