mirror of
https://github.com/Kong/insomnia.git
synced 2026-11-06 04:53:59 -05:00
test(insomnia-test): add the mock-bin server to the mock server
Port the mock-bin (:4090) responses used by the mock-server specs into misc/server and start it from index.ts. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d1246fc3a6
commit
f74e4e2869
2 files changed
+165
No files matched your search
@@ -27,6 +27,7 @@ import { startHttpEchoServers } from './http-echo.ts';
|
||||
import insomniaApi from './insomnia-api.ts';
|
||||
import { startMcpServers } from './mcp.ts';
|
||||
import { mockApi } from './mock-api.ts';
|
||||
import { startMockBinServer } from './mock-bin.ts';
|
||||
import { mtlsRouter } from './mtls.ts';
|
||||
import { oauthRoutes } from './oauth.ts';
|
||||
import { startOAuth2Server } from './oauth2.ts';
|
||||
@@ -309,6 +310,8 @@ startWsEchoServers();
|
||||
|
||||
startMcpServers();
|
||||
|
||||
startMockBinServer();
|
||||
|
||||
startHelloGrpcServers();
|
||||
|
||||
startOAuth2Server();
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import type { IncomingMessage, ServerResponse } from "node:http";
|
||||
import http from 'node:http';
|
||||
|
||||
const PORT = 4090;
|
||||
|
||||
/** @type {Map<string, any>} key: `${METHOD} ${compoundId}` -> HAR response */
|
||||
const bins = new Map<string, any>();
|
||||
/** @type {Map<string, any[]>} key: `${METHOD} ${compoundId}` -> HAR log entries */
|
||||
const logs = new Map<string, any[]>();
|
||||
|
||||
const CORS_HEADERS = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': '*',
|
||||
'Access-Control-Allow-Headers': '*',
|
||||
};
|
||||
|
||||
// A fixed response a spec can send a request to and then turn into a mock
|
||||
// route. It carries an explicit Content-Length (no chunked
|
||||
// Transfer-Encoding): a route created from a response replays that
|
||||
// response's headers as the *request* headers of its own "Test" request,
|
||||
// and a chunked GET would never finish. Mirror any change here into
|
||||
// `MOCK_BIN_SOURCE_RESPONSE` in constants.ts.
|
||||
const SOURCE_RESPONSE_PATH = '/source';
|
||||
const SOURCE_RESPONSE_STATUS = 202;
|
||||
const SOURCE_RESPONSE_BODY = { source: 'mock-bin-server' };
|
||||
const SOURCE_RESPONSE_HEADER_NAME = 'x-mock-source';
|
||||
const SOURCE_RESPONSE_HEADER_VALUE = 'mock-bin-server';
|
||||
|
||||
const HOP_BY_HOP_HEADERS = new Set([
|
||||
'connection',
|
||||
'content-length',
|
||||
'date',
|
||||
'keep-alive',
|
||||
'transfer-encoding',
|
||||
]);
|
||||
|
||||
const readBody = (req: IncomingMessage) =>
|
||||
new Promise<string>(resolve => {
|
||||
const chunks: any[] = [];
|
||||
req.on('data', (chunk: any) => chunks.push(chunk));
|
||||
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
||||
});
|
||||
|
||||
const send = (res: ServerResponse, status: number, body: string, headers: Record<string, any> = {}) => {
|
||||
res.writeHead(status, {
|
||||
'Content-Type': 'text/plain; charset=utf-8',
|
||||
'X-Content-Type-Options': 'nosniff',
|
||||
...CORS_HEADERS,
|
||||
...headers,
|
||||
});
|
||||
res.end(body);
|
||||
};
|
||||
|
||||
const handle = async (req: IncomingMessage, res: ServerResponse) => {
|
||||
const url = new URL(req.url!, `http://localhost:${PORT}`);
|
||||
const method = (req.method || 'GET').toUpperCase();
|
||||
|
||||
if (method === 'OPTIONS') {
|
||||
return send(res, 204, '');
|
||||
}
|
||||
|
||||
if (url.pathname === '/health') {
|
||||
return send(res, 200, 'ok');
|
||||
}
|
||||
|
||||
if (method === 'GET' && url.pathname === SOURCE_RESPONSE_PATH) {
|
||||
const body = JSON.stringify(SOURCE_RESPONSE_BODY);
|
||||
return send(res, SOURCE_RESPONSE_STATUS, body, {
|
||||
'Content-Type': 'application/json',
|
||||
'Content-Length': Buffer.byteLength(body),
|
||||
[SOURCE_RESPONSE_HEADER_NAME]: SOURCE_RESPONSE_HEADER_VALUE,
|
||||
});
|
||||
}
|
||||
|
||||
if (method === 'PUT' && url.pathname.startsWith('/bin/upsert/')) {
|
||||
const compoundId = decodeURIComponent(url.pathname.slice('/bin/upsert/'.length));
|
||||
const routeMethod = String(req.headers['insomnia-mock-method'] || 'GET').toUpperCase();
|
||||
bins.set(`${routeMethod} ${compoundId}`, JSON.parse((await readBody(req)) || '{}'));
|
||||
return send(res, 200, 'ok', { 'Content-Type': 'text/plain' });
|
||||
}
|
||||
|
||||
if (method === 'GET' && url.pathname.startsWith('/bin/log/')) {
|
||||
const compoundId = decodeURIComponent(url.pathname.slice('/bin/log/'.length));
|
||||
const routeMethod = String(req.headers['insomnia-mock-method'] || 'GET').toUpperCase();
|
||||
return send(
|
||||
res,
|
||||
200,
|
||||
JSON.stringify({
|
||||
log: {
|
||||
version: '1.2',
|
||||
creator: { name: 'mock-bin-server', version: '1.0.0' },
|
||||
entries: logs.get(`${routeMethod} ${compoundId}`) || [],
|
||||
},
|
||||
}),
|
||||
{ 'Content-Type': 'application/json' },
|
||||
);
|
||||
}
|
||||
|
||||
if (url.pathname.startsWith('/bin/')) {
|
||||
const compoundId = decodeURIComponent(url.pathname.slice('/bin/'.length));
|
||||
const key = `${method} ${compoundId}`;
|
||||
const body = await readBody(req);
|
||||
const entries = logs.get(key) || [];
|
||||
entries.push({
|
||||
startedDateTime: new Date().toISOString(),
|
||||
clientIPAddress: req.socket.remoteAddress || '',
|
||||
request: {
|
||||
method,
|
||||
url: `http://localhost:${PORT}${req.url}`,
|
||||
httpVersion: 'HTTP/1.1',
|
||||
headers: Object.entries(req.headers).map(([name, value]) => ({ name, value: String(value) })),
|
||||
queryString: [...url.searchParams.entries()].map(([name, value]) => ({ name, value })),
|
||||
cookies: [],
|
||||
headersSize: -1,
|
||||
bodySize: Buffer.byteLength(body),
|
||||
postData: body ? { mimeType: String(req.headers['content-type'] || ''), text: body } : undefined,
|
||||
},
|
||||
});
|
||||
logs.set(key, entries);
|
||||
|
||||
const har = bins.get(key);
|
||||
if (!har) {
|
||||
return send(res, 404, JSON.stringify({ error: 'no mock route' }), {
|
||||
'Content-Type': 'application/json',
|
||||
});
|
||||
}
|
||||
const headers: Record<string, any> = {};
|
||||
for (const { name, value } of har.headers || []) {
|
||||
if (name && !HOP_BY_HOP_HEADERS.has(name.toLowerCase())) headers[name] = value;
|
||||
}
|
||||
if (har.content?.mimeType && !Object.keys(headers).some(name => name.toLowerCase() === 'content-type')) {
|
||||
headers['Content-Type'] = har.content.mimeType;
|
||||
}
|
||||
res.writeHead(har.status || 200, har.statusText || undefined, { ...CORS_HEADERS, ...headers });
|
||||
return res.end(har.content?.text || '');
|
||||
}
|
||||
|
||||
if (method === 'DELETE' && url.pathname === '/_admin/reset') {
|
||||
bins.clear();
|
||||
logs.clear();
|
||||
return send(res, 200, 'ok');
|
||||
}
|
||||
|
||||
return send(res, 404, 'not found');
|
||||
};
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
handle(req, res).catch(error => {
|
||||
console.error('[mock-bin-server]', error);
|
||||
if (!res.headersSent) {
|
||||
send(res, 500, 'internal error');
|
||||
} else {
|
||||
res.end();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
export function startMockBinServer() {
|
||||
server.listen(PORT, '127.0.0.1', () => {
|
||||
console.log(`[mock-bin-server] listening on :${PORT}`);
|
||||
});
|
||||
}
|
||||
Reference in new issue
Block a user