openpencil/tests/engine/tauri/mcp-spawn.test.ts
Danila Poyarkov 8f35746060 fix(app): keep desktop startup console clean
- Load Prism JSX only after exposing the Prism runtime

- Treat unavailable MCP automation as optional during startup

- Connect the desktop automation bridge only after MCP is ready
2026-08-11 13:56:04 +03:00

181 lines
6.5 KiB
TypeScript

import { afterEach, describe, expect, test, vi } from 'bun:test'
import { getAutomationAuthToken, spawnMCPIfNeeded } from '@/app/automation/mcp/spawn'
import { clearTauriMocks, installTauriMockWindow, mockTauriIPC } from '#tests/helpers/tauri/mocks'
const DISCOVERY_PATH = '/mock/home/.openpencil/mcp.json'
const DISCOVERY_JSON = JSON.stringify({
pid: 1234,
socketPath: '/mock/home/.openpencil/mcp.sock',
httpPort: 7600,
authRequired: true,
authToken: 'discovery-token',
version: '0.0.0-test',
startedAt: new Date().toISOString()
})
afterEach(async () => {
await clearTauriMocks()
vi.restoreAllMocks()
Reflect.deleteProperty(globalThis, 'window')
Reflect.deleteProperty(globalThis, 'navigator')
Reflect.deleteProperty(globalThis, 'location')
})
describe('Tauri MCP spawning', () => {
test('retains an early startup failure for MCP-dependent features', async () => {
installTauriMockWindow()
Object.assign(globalThis.window, { location: { origin: 'tauri://localhost' } })
Object.defineProperty(globalThis, 'navigator', {
configurable: true,
value: { platform: 'Win32', userAgent: 'Windows' }
})
vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('', { status: 404 }))
await mockTauriIPC((cmd, args) => {
if (cmd === 'mcp_executable_available') return true
if (cmd === 'plugin:path|resolve_directory') return '/mock/home'
if (cmd === 'plugin:fs|exists') return false
if (cmd === 'plugin:shell|spawn') {
const onEvent = (args as { onEvent: { onmessage: (event: unknown) => void } }).onEvent
.onmessage
queueMicrotask(() => {
onEvent({
event: 'Stderr',
payload: Array.from(
new TextEncoder().encode('El sistema no puede encontrar el archivo especificado.')
)
})
onEvent({ event: 'Terminated', payload: { code: 1, signal: null } })
})
return 88
}
return null
})
expect(await spawnMCPIfNeeded()).toBeNull()
expect(getAutomationAuthToken()).rejects.toThrow('MCP server exited before startup completed')
})
test('retains unexpected spawn errors for MCP-dependent features', async () => {
installTauriMockWindow()
Object.assign(globalThis.window, { location: { origin: 'tauri://localhost' } })
Object.defineProperty(globalThis, 'navigator', {
configurable: true,
value: { platform: 'MacIntel', userAgent: 'Macintosh' }
})
vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('', { status: 404 }))
await mockTauriIPC((cmd) => {
if (cmd === 'mcp_executable_available') return true
if (cmd === 'plugin:path|resolve_directory') return '/mock/home'
if (cmd === 'plugin:fs|exists') return false
if (cmd === 'plugin:shell|spawn') throw new Error('shell permission denied')
return null
})
expect(await spawnMCPIfNeeded()).toBeNull()
expect(getAutomationAuthToken()).rejects.toThrow('shell permission denied')
})
test('retains a language-independent missing executable diagnostic', async () => {
installTauriMockWindow()
Object.assign(globalThis.window, { location: { origin: 'tauri://localhost' } })
Object.defineProperty(globalThis, 'navigator', {
configurable: true,
value: { platform: 'MacIntel', userAgent: 'Macintosh' }
})
vi.spyOn(globalThis, 'fetch').mockResolvedValue(new Response('', { status: 404 }))
await mockTauriIPC((cmd) => {
if (cmd === 'plugin:path|resolve_directory') return '/mock/home'
if (cmd === 'plugin:fs|exists') return false
if (cmd === 'mcp_executable_available') return false
return null
})
expect(await spawnMCPIfNeeded()).toBeNull()
expect(getAutomationAuthToken()).rejects.toThrow('MCP automation is not installed')
})
test('spawns MCP server with shell plugin when health check is missing', async () => {
installTauriMockWindow()
Object.assign(globalThis.window, { location: { origin: 'tauri://localhost' } })
Object.defineProperty(globalThis, 'navigator', {
configurable: true,
value: { platform: 'MacIntel' }
})
let healthChecks = 0
vi.spyOn(globalThis, 'fetch').mockImplementation(async () => {
healthChecks += 1
if (healthChecks === 1) return new Response('', { status: 404 })
return new Response(
JSON.stringify({
status: 'ok',
version: '0.0.0-test',
authRequired: true,
discoveryPath: DISCOVERY_PATH
}),
{ status: 200 }
)
})
let onEvent: ((event: unknown) => void) | null = null
const calls: Array<{ cmd: string; args: unknown }> = []
await mockTauriIPC((cmd, args) => {
calls.push({ cmd, args })
if (cmd === 'mcp_executable_available') return true
if (cmd === 'plugin:shell|spawn') {
expect(args).toMatchObject({
program: 'openpencil-mcp-http',
args: [],
options: {
env: {
PORT: '7600',
OPENPENCIL_MCP_AUTH_TOKEN: expect.any(String),
OPENPENCIL_MCP_CORS_ORIGIN: 'tauri://localhost',
OPENPENCIL_MCP_TCP: '1',
OPENPENCIL_MCP_ROOT: '/mock/home'
}
}
})
onEvent = (args as { onEvent: { onmessage: (event: unknown) => void } }).onEvent.onmessage
return 77
}
const pathArg = (args as { path?: string })?.path
if (cmd === 'plugin:fs|exists') {
return pathArg === DISCOVERY_PATH
}
if (cmd === 'plugin:fs|read_text_file') {
// Only return discovery data when reading the expected discovery path
if (pathArg === DISCOVERY_PATH) return new TextEncoder().encode(DISCOVERY_JSON)
return null
}
// Handle homeDir() IPC call from resolveTauriHomeDir
if (cmd === 'plugin:path|resolve_directory') {
return '/mock/home'
}
return null
})
const handle = await spawnMCPIfNeeded()
await expect(getAutomationAuthToken()).resolves.toBe('discovery-token')
onEvent?.({ event: 'Stderr', payload: [119, 97, 114, 110] })
handle?.disconnect()
await Promise.resolve()
expect(handle?.authToken).toBe('discovery-token')
expect(calls.some((c) => c.cmd === 'plugin:shell|spawn')).toBe(true)
expect(
calls.some(
(c) =>
c.cmd === 'plugin:fs|read_text_file' &&
(c.args as { path?: string })?.path === DISCOVERY_PATH
)
).toBe(true)
expect(calls.at(-1)).toEqual({ cmd: 'plugin:shell|kill', args: { cmd: 'killChild', pid: 77 } })
})
})