diff --git a/services/cowork-service/src/lib/ipc-bridge.test.ts b/services/cowork-service/src/lib/ipc-bridge.test.ts index fafed170..79721798 100644 --- a/services/cowork-service/src/lib/ipc-bridge.test.ts +++ b/services/cowork-service/src/lib/ipc-bridge.test.ts @@ -1,6 +1,15 @@ import { describe, expect, it, vi, beforeEach } from 'vitest'; import { IpcBridge, getIpcBridge, setIpcBridge } from './ipc-bridge.js'; +type TestableIpcBridge = IpcBridge & { + handleLine(line: string): void; + sendResponse(id: number, result?: unknown, error?: { code: number; message: string }): void; +}; + +function asTestableBridge(bridge: IpcBridge): TestableIpcBridge { + return bridge as unknown as TestableIpcBridge; +} + vi.mock('./config.js', () => ({ config: { RUST_RUNTIME_BIN: 'echo', @@ -88,7 +97,9 @@ describe('IpcBridge convenience methods', () => { it('getTaskStatus builds correct params', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 2, result: { id: 'task-1', status: 'running' }, + jsonrpc: '2.0', + id: 2, + result: { id: 'task-1', status: 'running' }, }); const auth = { userId: 'u1' }; @@ -99,27 +110,39 @@ describe('IpcBridge convenience methods', () => { it('cancelTask builds correct params', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 3, result: { status: 'cancelled' }, + jsonrpc: '2.0', + id: 3, + result: { status: 'cancelled' }, }); await bridge.cancelTask('task-1', { userId: 'u1' }); - expect(callSpy).toHaveBeenCalledWith('cancel_task', { taskId: 'task-1', auth: { userId: 'u1' } }); + expect(callSpy).toHaveBeenCalledWith('cancel_task', { + taskId: 'task-1', + auth: { userId: 'u1' }, + }); }); it('listTasks with status filter', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 4, result: { tasks: [] }, + jsonrpc: '2.0', + id: 4, + result: { tasks: [] }, }); await bridge.listTasks({ userId: 'u1' }, 'pending'); - expect(callSpy).toHaveBeenCalledWith('list_tasks', { auth: { userId: 'u1' }, status: 'pending' }); + expect(callSpy).toHaveBeenCalledWith('list_tasks', { + auth: { userId: 'u1' }, + status: 'pending', + }); }); it('listTasks without status filter', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 5, result: { tasks: [] }, + jsonrpc: '2.0', + id: 5, + result: { tasks: [] }, }); await bridge.listTasks({ userId: 'u1' }); @@ -129,10 +152,15 @@ describe('IpcBridge convenience methods', () => { it('updateFlags builds correct params', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 6, result: { updated: 2 }, + jsonrpc: '2.0', + id: 6, + result: { updated: 2 }, }); - await bridge.updateFlags({ sandbox_enabled: true, plugins_enabled: false }, { userId: 'admin' }); + await bridge.updateFlags( + { sandbox_enabled: true, plugins_enabled: false }, + { userId: 'admin' } + ); expect(callSpy).toHaveBeenCalledWith('update_flags', { flags: { sandbox_enabled: true, plugins_enabled: false }, auth: { userId: 'admin' }, @@ -142,7 +170,9 @@ describe('IpcBridge convenience methods', () => { it('flushAudit builds correct params', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 7, result: { count: 0, entries: [] }, + jsonrpc: '2.0', + id: 7, + result: { count: 0, entries: [] }, }); await bridge.flushAudit({ userId: 'admin' }); @@ -152,7 +182,9 @@ describe('IpcBridge convenience methods', () => { it('recordSpend builds correct params', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 8, result: { totalCostUsd: 0.015, allowed: true }, + jsonrpc: '2.0', + id: 8, + result: { totalCostUsd: 0.015, allowed: true }, }); await bridge.recordSpend('gpt-4o', 1000, 500, 0.015, { userId: 'u1' }, 'task-1'); @@ -169,7 +201,9 @@ describe('IpcBridge convenience methods', () => { it('updateVerdict builds correct params', async () => { const bridge = new IpcBridge(); const callSpy = vi.spyOn(bridge, 'call').mockResolvedValue({ - jsonrpc: '2.0', id: 9, result: { allowed: false }, + jsonrpc: '2.0', + id: 9, + result: { allowed: false }, }); await bridge.updateVerdict({ verdict: 'block', spent_usd: 10 }, { userId: 'admin' }); @@ -191,7 +225,9 @@ describe('reverse IPC (incoming requests)', () => { it('handleLine routes incoming requests to the registered handler', async () => { const bridge = new IpcBridge(); - const handler = vi.fn().mockResolvedValue({ response: 'hello', provider: 'test', model: 'test-model' }); + const handler = vi + .fn() + .mockResolvedValue({ response: 'hello', provider: 'test', model: 'test-model' }); bridge.onIncomingRequest(handler); // Simulate an incoming JSON-RPC request line from Rust @@ -202,11 +238,11 @@ describe('reverse IPC (incoming requests)', () => { params: { messages: [{ role: 'user', content: 'hi' }], model: 'test-model' }, }); - // Access private handleLine via any cast (test-only) - (bridge as any).handleLine(incomingLine); + // Access private handleLine through the typed test bridge. + asTestableBridge(bridge).handleLine(incomingLine); // Wait for async handler - await new Promise((r) => setTimeout(r, 10)); + await new Promise(r => setTimeout(r, 10)); expect(handler).toHaveBeenCalledWith('intercept_llm', { messages: [{ role: 'user', content: 'hi' }], @@ -216,7 +252,7 @@ describe('reverse IPC (incoming requests)', () => { it('handleLine sends error response when no handler is registered', () => { const bridge = new IpcBridge(); - const sendSpy = vi.spyOn(bridge as any, 'sendResponse'); + const sendSpy = vi.spyOn(asTestableBridge(bridge), 'sendResponse'); const incomingLine = JSON.stringify({ jsonrpc: '2.0', @@ -225,7 +261,7 @@ describe('reverse IPC (incoming requests)', () => { params: {}, }); - (bridge as any).handleLine(incomingLine); + asTestableBridge(bridge).handleLine(incomingLine); expect(sendSpy).toHaveBeenCalledWith(99, undefined, { code: -32601, @@ -237,7 +273,7 @@ describe('reverse IPC (incoming requests)', () => { const bridge = new IpcBridge(); const handler = vi.fn().mockRejectedValue(new Error('LLM router not initialized')); bridge.onIncomingRequest(handler); - const sendSpy = vi.spyOn(bridge as any, 'sendResponse'); + const sendSpy = vi.spyOn(asTestableBridge(bridge), 'sendResponse'); const incomingLine = JSON.stringify({ jsonrpc: '2.0', @@ -246,8 +282,8 @@ describe('reverse IPC (incoming requests)', () => { params: { messages: [] }, }); - (bridge as any).handleLine(incomingLine); - await new Promise((r) => setTimeout(r, 10)); + asTestableBridge(bridge).handleLine(incomingLine); + await new Promise(r => setTimeout(r, 10)); expect(sendSpy).toHaveBeenCalledWith(50, undefined, { code: -32000, @@ -262,12 +298,12 @@ describe('reverse IPC (incoming requests)', () => { // Response (no method field) — should not call handler const responseLine = JSON.stringify({ jsonrpc: '2.0', id: 1, result: { ok: true } }); - (bridge as any).handleLine(responseLine); + asTestableBridge(bridge).handleLine(responseLine); expect(handler).not.toHaveBeenCalled(); // Request (has method field) — should call handler const requestLine = JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'test', params: {} }); - (bridge as any).handleLine(requestLine); + asTestableBridge(bridge).handleLine(requestLine); expect(handler).toHaveBeenCalledWith('test', {}); }); }); diff --git a/services/cowork-service/src/modules/agent-runtime/routes.test.ts b/services/cowork-service/src/modules/agent-runtime/routes.test.ts index 65182cbd..949d80ce 100644 --- a/services/cowork-service/src/modules/agent-runtime/routes.test.ts +++ b/services/cowork-service/src/modules/agent-runtime/routes.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import Fastify from 'fastify'; import { agentRuntimeRoutes } from './routes.js'; -import { setIpcBridge } from '../../lib/ipc-bridge.js'; +import { setIpcBridge, type IpcBridge } from '../../lib/ipc-bridge.js'; vi.mock('../../lib/config.js', () => ({ config: { @@ -30,7 +30,7 @@ beforeEach(async () => { setIpcBridge({ isRunning: true, call, - } as any); + } as unknown as IpcBridge); app = Fastify({ logger: false }); app.decorateRequest('jwtPayload', null); diff --git a/services/cowork-service/src/modules/marketplace/routes.test.ts b/services/cowork-service/src/modules/marketplace/routes.test.ts index a1578af4..94c89cc2 100644 --- a/services/cowork-service/src/modules/marketplace/routes.test.ts +++ b/services/cowork-service/src/modules/marketplace/routes.test.ts @@ -18,8 +18,11 @@ vi.stubGlobal('fetch', mockFetch); function buildApp() { const app = Fastify({ logger: false }); app.decorateRequest('jwtPayload', null); - app.addHook('onRequest', async (req) => { - (req as any).jwtPayload = { sub: 'user-1', role: 'user' }; + app.addHook('onRequest', async req => { + (req as unknown as { jwtPayload: { sub: string; role: string } }).jwtPayload = { + sub: 'user-1', + role: 'user', + }; }); app.register(marketplaceRoutes); return app; diff --git a/services/cowork-service/src/modules/plugins/routes.test.ts b/services/cowork-service/src/modules/plugins/routes.test.ts index 92855568..5b1188a7 100644 --- a/services/cowork-service/src/modules/plugins/routes.test.ts +++ b/services/cowork-service/src/modules/plugins/routes.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import Fastify from 'fastify'; import { pluginRoutes } from './routes.js'; -import { setIpcBridge } from '../../lib/ipc-bridge.js'; +import { setIpcBridge, type IpcBridge } from '../../lib/ipc-bridge.js'; vi.mock('../../lib/product-config.js', () => ({ PRODUCT_ID: 'clawcowork', @@ -21,7 +21,7 @@ beforeEach(async () => { setIpcBridge({ isRunning: true, call, - } as any); + } as unknown as IpcBridge); app = Fastify({ logger: false }); app.decorateRequest('jwtPayload', null); @@ -128,7 +128,7 @@ describe('plugin routes', () => { // Spin up a fresh app with a preHandler that injects an admin jwtPayload. const adminApp = Fastify({ logger: false }); - (adminApp as any).decorateRequest('jwtPayload', null); + adminApp.decorateRequest('jwtPayload', null); adminApp.addHook('preHandler', async req => { (req as unknown as { jwtPayload: unknown }).jwtPayload = { sub: 'demo-user', @@ -157,10 +157,10 @@ describe('plugin routes', () => { }); it('returns 400 when IPC bridge is unavailable', async () => { - setIpcBridge({ isRunning: false, call } as any); + setIpcBridge({ isRunning: false, call } as unknown as IpcBridge); const downApp = Fastify({ logger: false }); - (downApp as any).decorateRequest('jwtPayload', null); + downApp.decorateRequest('jwtPayload', null); downApp.addHook('preHandler', async req => { (req as unknown as { jwtPayload: unknown }).jwtPayload = { role: 'admin' }; }); @@ -176,7 +176,7 @@ describe('plugin routes', () => { call.mockResolvedValue({ error: { code: -32003, message: 'admin required' } }); const adminApp = Fastify({ logger: false }); - (adminApp as any).decorateRequest('jwtPayload', null); + adminApp.decorateRequest('jwtPayload', null); adminApp.addHook('preHandler', async req => { (req as unknown as { jwtPayload: unknown }).jwtPayload = { role: 'admin' }; }); @@ -191,7 +191,7 @@ describe('plugin routes', () => { call.mockResolvedValue({ error: { code: -32603, message: 'internal boom' } }); const adminApp = Fastify({ logger: false }); - (adminApp as any).decorateRequest('jwtPayload', null); + adminApp.decorateRequest('jwtPayload', null); adminApp.addHook('preHandler', async req => { (req as unknown as { jwtPayload: unknown }).jwtPayload = { role: 'admin' }; }); @@ -207,7 +207,7 @@ describe('plugin routes', () => { call.mockResolvedValue({ result: { loaded: 'not-a-number' } }); const adminApp = Fastify({ logger: false }); - (adminApp as any).decorateRequest('jwtPayload', null); + adminApp.decorateRequest('jwtPayload', null); adminApp.addHook('preHandler', async req => { (req as unknown as { jwtPayload: unknown }).jwtPayload = { role: 'admin' }; }); diff --git a/services/cowork-service/src/modules/schedule/routes.test.ts b/services/cowork-service/src/modules/schedule/routes.test.ts index ec3d4883..e7981217 100644 --- a/services/cowork-service/src/modules/schedule/routes.test.ts +++ b/services/cowork-service/src/modules/schedule/routes.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import Fastify from 'fastify'; import { scheduleRoutes } from './routes.js'; -import { setIpcBridge } from '../../lib/ipc-bridge.js'; +import { setIpcBridge, type IpcBridge } from '../../lib/ipc-bridge.js'; vi.mock('../../lib/product-config.js', () => ({ PRODUCT_ID: 'clawcowork', @@ -21,7 +21,7 @@ beforeEach(async () => { setIpcBridge({ isRunning: true, call, - } as any); + } as unknown as IpcBridge); app = Fastify({ logger: false }); app.decorateRequest('jwtPayload', null); diff --git a/services/cowork-service/src/modules/sessions/routes.test.ts b/services/cowork-service/src/modules/sessions/routes.test.ts index 1a839c5d..00df399f 100644 --- a/services/cowork-service/src/modules/sessions/routes.test.ts +++ b/services/cowork-service/src/modules/sessions/routes.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import Fastify from 'fastify'; import { sessionRoutes } from './routes.js'; -import { setIpcBridge } from '../../lib/ipc-bridge.js'; +import { setIpcBridge, type IpcBridge } from '../../lib/ipc-bridge.js'; vi.mock('../../lib/product-config.js', () => ({ PRODUCT_ID: 'clawcowork', @@ -21,7 +21,7 @@ beforeEach(async () => { setIpcBridge({ isRunning: true, call, - } as any); + } as unknown as IpcBridge); app = Fastify({ logger: false }); app.decorateRequest('jwtPayload', null);