chore(cowork-service): type test doubles

Replace cowork-service test any casts with structural test doubles for IPC bridges, Fastify request payloads, and private IPC bridge probes.

Verification: pnpm --filter @lysnrai/cowork-service build; pnpm --filter @lysnrai/cowork-service test; pnpm --filter @lysnrai/cowork-service exec eslint . --ext .ts,.tsx; pnpm lint.
This commit is contained in:
Saravana Achu Mac 2026-05-04 15:25:43 -07:00
parent 9625999ad4
commit 6978ddb209
6 changed files with 77 additions and 38 deletions

View File

@ -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', {});
});
});

View File

@ -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);

View File

@ -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;

View File

@ -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' };
});

View File

@ -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);

View File

@ -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);