201 lines
5.1 KiB
TypeScript
201 lines
5.1 KiB
TypeScript
#!/usr/bin/env bun
|
|
|
|
import { createServer, type IncomingMessage, type ServerResponse } from 'node:http';
|
|
|
|
const GMAIL_TOOLKIT = {
|
|
name: 'Gmail',
|
|
slug: 'gmail',
|
|
auth_schemes: ['OAUTH2'],
|
|
composio_managed_auth_schemes: ['OAUTH2'],
|
|
is_local_toolkit: false,
|
|
meta: {
|
|
description: 'Gmail toolkit for deterministic CLI e2e tests.',
|
|
categories: [],
|
|
created_at: '2024-01-01T00:00:00.000Z',
|
|
updated_at: '2024-01-01T00:00:00.000Z',
|
|
available_versions: ['2024.01.01'],
|
|
tools_count: 40,
|
|
triggers_count: 2,
|
|
},
|
|
no_auth: false,
|
|
} as const;
|
|
|
|
const GMAIL_TOOLKIT_DETAILED = {
|
|
name: 'Gmail',
|
|
slug: 'gmail',
|
|
is_local_toolkit: false,
|
|
composio_managed_auth_schemes: ['OAUTH2'],
|
|
no_auth: false,
|
|
meta: {
|
|
description: 'Gmail toolkit for deterministic CLI e2e tests.',
|
|
categories: [],
|
|
created_at: '2024-01-01T00:00:00.000Z',
|
|
updated_at: '2024-01-01T00:00:00.000Z',
|
|
available_versions: ['2024.01.01'],
|
|
tools_count: 40,
|
|
triggers_count: 2,
|
|
},
|
|
auth_config_details: [
|
|
{
|
|
mode: 'OAUTH2',
|
|
name: 'OAuth 2.0',
|
|
fields: {
|
|
auth_config_creation: {
|
|
required: [],
|
|
optional: [],
|
|
},
|
|
connected_account_initiation: {
|
|
required: [],
|
|
optional: [],
|
|
},
|
|
},
|
|
},
|
|
],
|
|
} as const;
|
|
|
|
export interface MockToolkitsServer {
|
|
readonly hostBaseUrl: string;
|
|
readonly dockerBaseUrl: string;
|
|
readonly requests: string[];
|
|
readonly close: () => Promise<void>;
|
|
}
|
|
|
|
const sendJson = (res: ServerResponse, statusCode: number, body: unknown): void => {
|
|
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
|
|
res.end(JSON.stringify(body));
|
|
};
|
|
|
|
const matchingToolkits = (search: string | null): ReadonlyArray<typeof GMAIL_TOOLKIT> => {
|
|
if (search === null || search.length === 0) {
|
|
return [GMAIL_TOOLKIT];
|
|
}
|
|
|
|
const normalized = search.toLowerCase();
|
|
if (normalized === 'gmail' || normalized === 'gmai') {
|
|
return [GMAIL_TOOLKIT];
|
|
}
|
|
|
|
return [];
|
|
};
|
|
|
|
const parseLimit = (req: IncomingMessage): number => {
|
|
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);
|
|
const parsed = Number.parseInt(url.searchParams.get('limit') ?? '', 10);
|
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : 30;
|
|
};
|
|
|
|
export async function startMockToolkitsListServer(options?: {
|
|
host?: string;
|
|
port?: number;
|
|
}): Promise<MockToolkitsServer> {
|
|
const host = options?.host ?? '0.0.0.0';
|
|
const port = options?.port ?? 0;
|
|
const requests: string[] = [];
|
|
|
|
const server = createServer((req, res) => {
|
|
const url = new URL(req.url ?? '/', `http://${req.headers.host ?? '127.0.0.1'}`);
|
|
const method = req.method ?? 'GET';
|
|
requests.push(`${method} ${url.pathname}${url.search}`);
|
|
|
|
if (
|
|
method === 'GET' &&
|
|
(url.pathname === '/api/v3/toolkits' || url.pathname === '/api/v3.1/toolkits')
|
|
) {
|
|
const items = matchingToolkits(url.searchParams.get('search')).slice(0, parseLimit(req));
|
|
sendJson(res, 200, {
|
|
items,
|
|
total_items: items.length,
|
|
total_pages: 1,
|
|
current_page: 1,
|
|
next_cursor: null,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (
|
|
method === 'GET' &&
|
|
(url.pathname === '/api/v3/toolkits/gmail' || url.pathname === '/api/v3.1/toolkits/gmail')
|
|
) {
|
|
sendJson(res, 200, GMAIL_TOOLKIT_DETAILED);
|
|
return;
|
|
}
|
|
|
|
if (
|
|
method === 'GET' &&
|
|
(url.pathname.startsWith('/api/v3/toolkits/') ||
|
|
url.pathname.startsWith('/api/v3.1/toolkits/'))
|
|
) {
|
|
sendJson(res, 404, {
|
|
error: `Toolkit not found: ${url.pathname.split('/').at(-1) ?? 'unknown'}`,
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (method === 'POST' && url.pathname === '/api/v3/cli/analytics') {
|
|
sendJson(res, 204, {});
|
|
return;
|
|
}
|
|
|
|
sendJson(res, 404, {
|
|
error: `Unhandled mock route: ${method} ${url.pathname}`,
|
|
});
|
|
});
|
|
|
|
await new Promise<void>((resolve, reject) => {
|
|
server.once('error', reject);
|
|
server.listen({ host, port }, () => {
|
|
server.off('error', reject);
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
const address = server.address();
|
|
if (address === null || typeof address === 'string') {
|
|
throw new Error('Failed to bind mock toolkits list server to a TCP port');
|
|
}
|
|
|
|
const close = () =>
|
|
new Promise<void>((resolve, reject) => {
|
|
server.close(error => {
|
|
if (error) {
|
|
reject(error);
|
|
return;
|
|
}
|
|
resolve();
|
|
});
|
|
});
|
|
|
|
return {
|
|
hostBaseUrl: `http://127.0.0.1:${address.port}`,
|
|
dockerBaseUrl: `http://host.docker.internal:${address.port}`,
|
|
requests,
|
|
close,
|
|
};
|
|
}
|
|
|
|
const parsePortArg = (): number | undefined => {
|
|
const index = process.argv.indexOf('--port');
|
|
if (index === -1) return undefined;
|
|
|
|
const value = Number.parseInt(process.argv[index + 1] ?? '', 10);
|
|
return Number.isFinite(value) ? value : undefined;
|
|
};
|
|
|
|
if (import.meta.main) {
|
|
const server = await startMockToolkitsListServer({ port: parsePortArg() });
|
|
|
|
const shutdown = async () => {
|
|
await server.close();
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on('SIGINT', () => {
|
|
void shutdown();
|
|
});
|
|
process.on('SIGTERM', () => {
|
|
void shutdown();
|
|
});
|
|
|
|
await new Promise(() => {});
|
|
}
|