/** Opt-in acceptance against a running local IDE backend. No credentials on disk. */ import https from 'node:https' import crypto from 'node:crypto' import type { PeerCertificate } from 'node:tls' import { expect, it } from 'vitest' import { WebSocket } from 'ws' import { createSelfSignedCertificate } from './mobile-lan-cert' import { createLanProxy } from './mobile-lan-proxy' it.skipIf(!process.env.AITURK_TEST_BACKEND_PORT)( 'real backend: pinned TLS, screens, cron lifecycle and chat socket', async () => { const port = Number(process.env.AITURK_TEST_BACKEND_PORT) expect(Number.isInteger(port) && port > 0 && port < 65536).toBe(true) const baseUrl = `http://127.0.0.1:${port}` const root = await (await fetch(baseUrl)).text() const session = root.match(/window\.__HERMES_SESSION_TOKEN__\s*=\s*"([^"]+)"/)?.[1] expect(Boolean(session)).toBe(true) const identity = createSelfSignedCertificate() const checkServerIdentity = (_host: string, cert: PeerCertificate) => crypto.createHash('sha256').update(cert.raw).digest('base64url') === identity.fingerprint ? undefined : new Error('Certificate pin mismatch') const proxy = createLanProxy({ tls: identity, version: 'acceptance', pcName: 'acceptance', resolveBackend: async profile => { if (profile !== 'default') throw new Error('Acceptance may only use the default profile') return { baseUrl, token: session! } } }) const proxyPort = await proxy.listen(0, '127.0.0.1') let deviceToken = '' let createdId: string | undefined const call = (path: string, method = 'GET', body?: unknown): Promise<{ status: number; data: any }> => new Promise((resolve, reject) => { const req = https.request( { hostname: '127.0.0.1', port: proxyPort, path, method, ca: identity.cert, checkServerIdentity, headers: { ...(deviceToken ? { Authorization: `Bearer ${deviceToken}` } : {}), 'Content-Type': 'application/json' } }, res => { let value = '' res.on('data', chunk => (value += chunk)) res.on('end', () => { try { resolve({ status: res.statusCode!, data: value ? JSON.parse(value) : null }) } catch { reject(new Error(`Non-JSON response: ${path}`)) } }) } ) req.setTimeout(60000, () => req.destroy(new Error(`Timeout: ${path}`))) req.on('error', reject) req.end(body === undefined ? undefined : JSON.stringify(body)) }) try { const pair = await call('/aiturk/pair/claim', 'POST', { code: proxy.beginPairing().code, device_name: 'Acceptance ephemeral' }) expect(pair.status).toBe(200) deviceToken = pair.data.token for (const endpoint of [ 'health', 'profiles', 'sessions', 'cron/jobs', 'cron/blueprints', 'cron/delivery-targets', 'model/info', 'skills', 'tools/toolsets', 'mcp/servers', 'plugins/kanban/boards' ]) { const response = await call(`/api/${endpoint}?profile=default`) expect(response.status, endpoint).toBe(200) } const created = await call('/api/cron/jobs?profile=default', 'POST', { name: `AITURK mobile acceptance ${Date.now()}`, schedule: '0 0 1 1 *', prompt: 'Temporary acceptance record; deleted immediately without execution.', deliver: 'local' }) expect(created.status).toBe(200) createdId = created.data.id expect(typeof createdId).toBe('string') const route = `/api/cron/jobs/${encodeURIComponent(createdId!)}` expect((await call(`${route}/pause?profile=default`, 'POST')).status).toBe(200) expect( (await call(`${route}?profile=default`, 'PUT', { updates: { name: 'AITURK acceptance updated' } })).status ).toBe(200) expect((await call(`${route}/resume?profile=default`, 'POST')).status).toBe(200) await new Promise((resolve, reject) => { const ws = new WebSocket(`wss://127.0.0.1:${proxyPort}/api/ws?profile=default`, { ca: identity.cert, checkServerIdentity, headers: { Authorization: `Bearer ${deviceToken}` } }) const timer = setTimeout(() => { ws.terminate() reject(new Error('Chat ping timed out')) }, 15000) ws.on('open', () => ws.send(JSON.stringify({ jsonrpc: '2.0', id: 123, method: 'gateway.ping', params: {} }))) ws.on('message', raw => { const message = JSON.parse(String(raw)) if (message.id === 123) { clearTimeout(timer) ws.close() message.error ? reject(new Error('Chat ping rejected')) : resolve() } }) ws.on('error', error => { clearTimeout(timer) reject(error) }) }) } finally { if (createdId) { const route = `/api/cron/jobs/${encodeURIComponent(createdId)}?profile=default` expect((await call(route, 'DELETE')).status).toBe(200) expect((await call(route)).status).toBe(404) } await proxy.stop() } }, 180000 )