// 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() = /out/main 이다. vi.mocked(app.getAppPath).mockReturnValue(path.join(desktopDir, 'out', 'main')) resetPathCache() expect(getAppRoot()).toBe(desktopDir) }) it('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') }) it('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() } }) })