Add integration coverage for companion platform clients
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
|
||||
import { resolve } from 'path';
|
||||
import { createServer } from 'net';
|
||||
import type { GatewayServerConfig } from '../gateway/server.js';
|
||||
import { GatewayServer } from '../gateway/server.js';
|
||||
import { CompanionRuntimeClient } from './runtimeClient.js';
|
||||
import {
|
||||
AndroidCompanionClient,
|
||||
IOSCompanionClient,
|
||||
MacOSCompanionClient,
|
||||
} from './platformClients.js';
|
||||
|
||||
async function canListenOnLocalhost(): Promise<boolean> {
|
||||
return new Promise((resolvePromise) => {
|
||||
const s = createServer();
|
||||
s.once('error', () => resolvePromise(false));
|
||||
s.listen(0, '127.0.0.1', () => {
|
||||
s.close(() => resolvePromise(true));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
const mockSession = {
|
||||
id: 'test',
|
||||
addMessage: vi.fn(),
|
||||
getHistory: vi.fn(() => []),
|
||||
clear: vi.fn(),
|
||||
setHistory: vi.fn(),
|
||||
replaceHistory: vi.fn(),
|
||||
};
|
||||
|
||||
const mockSessionManager = {
|
||||
getSession: vi.fn(() => mockSession),
|
||||
listSessions: vi.fn(() => ['ws:test']),
|
||||
transferSession: vi.fn(),
|
||||
closeSession: vi.fn(),
|
||||
};
|
||||
|
||||
const mockModelClient = {
|
||||
chat: vi.fn(async () => ({
|
||||
content: 'Hello from Flynn!',
|
||||
stopReason: 'end_turn',
|
||||
usage: { inputTokens: 10, outputTokens: 5 },
|
||||
})),
|
||||
};
|
||||
|
||||
const mockToolRegistry = {
|
||||
register: vi.fn(),
|
||||
get: vi.fn(),
|
||||
list: vi.fn(() => []),
|
||||
filteredList: vi.fn(() => []),
|
||||
toAnthropicFormat: vi.fn(() => []),
|
||||
toOpenAIFormat: vi.fn(() => []),
|
||||
filteredToAnthropicFormat: vi.fn(() => []),
|
||||
filteredToOpenAIFormat: vi.fn(() => []),
|
||||
};
|
||||
|
||||
const mockToolExecutor = {
|
||||
execute: vi.fn(async () => ({ success: true, output: 'ok' })),
|
||||
};
|
||||
|
||||
const TEST_PORT = 18912;
|
||||
const TEST_TOKEN = 'platform-clients-token';
|
||||
|
||||
let LISTEN_ALLOWED = true;
|
||||
let server: GatewayServer;
|
||||
|
||||
beforeAll(async () => {
|
||||
LISTEN_ALLOWED = await canListenOnLocalhost();
|
||||
if (!LISTEN_ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
server = new GatewayServer({
|
||||
port: TEST_PORT,
|
||||
sessionManager: mockSessionManager as unknown as GatewayServerConfig['sessionManager'],
|
||||
modelClient: mockModelClient,
|
||||
systemPrompt: 'Test prompt',
|
||||
toolRegistry: mockToolRegistry as unknown as GatewayServerConfig['toolRegistry'],
|
||||
toolExecutor: mockToolExecutor as unknown as GatewayServerConfig['toolExecutor'],
|
||||
version: '0.1.0-test',
|
||||
uiDir: resolve(import.meta.dirname, '../gateway/ui'),
|
||||
auth: { token: TEST_TOKEN },
|
||||
nodes: {
|
||||
enabled: true,
|
||||
allowedRoles: ['companion'],
|
||||
featureGates: { 'ui.canvas': true },
|
||||
locationEnabled: true,
|
||||
pushEnabled: true,
|
||||
},
|
||||
});
|
||||
|
||||
await server.start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (!LISTEN_ALLOWED) {
|
||||
return;
|
||||
}
|
||||
await server.stop();
|
||||
});
|
||||
|
||||
function createRuntime(): CompanionRuntimeClient {
|
||||
return new CompanionRuntimeClient({
|
||||
url: `ws://127.0.0.1:${TEST_PORT}`,
|
||||
token: TEST_TOKEN,
|
||||
});
|
||||
}
|
||||
|
||||
describe('platform clients integration', () => {
|
||||
it('macOS companion wrapper registers and writes status with platform pinning', async () => {
|
||||
if (!LISTEN_ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const runtime = createRuntime();
|
||||
const client = new MacOSCompanionClient({ runtime, nodeId: 'macos-e2e' });
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.register();
|
||||
const status = await client.setStatus({
|
||||
appVersion: '1.0.0',
|
||||
statusText: 'menu-bar-active',
|
||||
powerSource: 'ac',
|
||||
});
|
||||
|
||||
expect(status.updated).toBe(true);
|
||||
expect(status.status.platform).toBe('macos');
|
||||
|
||||
const nodes = await client.listNodes();
|
||||
const entry = nodes.nodes.find((n) => n.nodeId === 'macos-e2e');
|
||||
expect(entry?.status?.platform).toBe('macos');
|
||||
} finally {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
it('iOS companion wrapper uses APNs push flow', async () => {
|
||||
if (!LISTEN_ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const runtime = createRuntime();
|
||||
const client = new IOSCompanionClient({ runtime, nodeId: 'ios-e2e' });
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.register();
|
||||
const push = await client.registerPushToken({
|
||||
token: 'd'.repeat(64),
|
||||
topic: 'dev.flynn.ios',
|
||||
environment: 'sandbox',
|
||||
});
|
||||
|
||||
expect(push.updated).toBe(true);
|
||||
expect(push.push.provider).toBe('apns');
|
||||
|
||||
const nodes = await client.listNodes();
|
||||
const entry = nodes.nodes.find((n) => n.nodeId === 'ios-e2e');
|
||||
expect(entry?.push?.provider).toBe('apns');
|
||||
} finally {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
|
||||
it('Android companion wrapper uses FCM push flow', async () => {
|
||||
if (!LISTEN_ALLOWED) {
|
||||
return;
|
||||
}
|
||||
|
||||
const runtime = createRuntime();
|
||||
const client = new AndroidCompanionClient({ runtime, nodeId: 'android-e2e' });
|
||||
await client.connect();
|
||||
|
||||
try {
|
||||
await client.register();
|
||||
const push = await client.registerPushToken('e'.repeat(64));
|
||||
|
||||
expect(push.updated).toBe(true);
|
||||
expect(push.push.provider).toBe('fcm');
|
||||
|
||||
const nodes = await client.listNodes();
|
||||
const entry = nodes.nodes.find((n) => n.nodeId === 'android-e2e');
|
||||
expect(entry?.push?.provider).toBe('fcm');
|
||||
} finally {
|
||||
client.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user