refactor with express and different proxy engine + huge perf increase

This commit is contained in:
Luke Hagar
2025-03-21 21:28:59 -05:00
parent bf03c22bc3
commit 6de97e7b88
67 changed files with 5460 additions and 1934 deletions

View File

@@ -38,9 +38,10 @@ interface User {
}
describe('Arbiter Integration Tests', () => {
const targetPort = 3001;
const proxyPort = 3002;
const docsPort = 3003;
// Use different ports to avoid conflicts with other tests
const targetPort = 4001;
const proxyPort = 4002;
const docsPort = 4003;
let targetServer: any;
let proxyServer: any;
@@ -59,7 +60,8 @@ describe('Arbiter Integration Tests', () => {
targetApi.post('/users', async (c) => {
const body = await c.req.json();
return c.json({ id: 3, ...body }, 201);
c.status(201);
return c.json({ id: 3, ...body });
});
targetApi.get('/users/:id', (c) => {
@@ -70,11 +72,23 @@ describe('Arbiter Integration Tests', () => {
targetApi.get('/secure', (c) => {
const apiKey = c.req.header('x-api-key');
if (apiKey !== 'test-key') {
return c.json({ error: 'Unauthorized' }, 401);
c.status(401);
return c.json({ error: 'Unauthorized' });
}
return c.json({ message: 'Secret data' });
});
// Add endpoint for query parameter test
targetApi.get('/users/search', (c) => {
const limit = c.req.query('limit');
const sort = c.req.query('sort');
return c.json({
results: [{ id: 1, name: 'John Doe' }],
limit: limit ? parseInt(limit) : 10,
sort: sort || 'asc'
});
});
beforeAll(async () => {
// Start the target API server
targetServer = serve({
@@ -83,18 +97,15 @@ describe('Arbiter Integration Tests', () => {
});
// Start Arbiter servers
const servers = await startServers({
const { proxyServer: proxy, docsServer: docs } = await startServers({
target: `http://localhost:${targetPort}`,
proxyPort,
docsPort,
verbose: false,
proxyPort: proxyPort,
docsPort: docsPort,
verbose: false
});
proxyServer = servers.proxyServer;
docsServer = servers.docsServer;
// Wait a bit to ensure servers are ready
await new Promise((resolve) => setTimeout(resolve, 1000));
proxyServer = proxy;
docsServer = docs;
});
afterAll(() => {
@@ -164,12 +175,18 @@ describe('Arbiter Integration Tests', () => {
expect(spec.paths?.['/users']?.post).toBeDefined();
expect(spec.paths?.['/users/{id}']?.get).toBeDefined();
// Validate schemas
expect(spec.components?.schemas).toBeDefined();
const userSchema = spec.components?.schemas?.User as OpenAPIV3_1.SchemaObject;
expect(userSchema).toBeDefined();
expect(userSchema.properties?.id).toBeDefined();
expect(userSchema.properties?.name).toBeDefined();
// Check request body schema
expect(spec.paths?.['/users']?.post?.requestBody).toBeDefined();
const requestBody = spec.paths?.['/users']?.post?.requestBody as OpenAPIV3_1.RequestBodyObject;
expect(requestBody.content?.['application/json']).toBeDefined();
expect(requestBody.content?.['application/json'].schema).toBeDefined();
// Validate schema properties based on what we sent in the POST request
const schema = requestBody.content?.['application/json'].schema as OpenAPIV3_1.SchemaObject;
expect(schema).toBeDefined();
expect(schema.type).toBe('object');
expect(schema.properties?.name).toBeDefined();
expect((schema.properties?.name as OpenAPIV3_1.SchemaObject).type).toBe('string');
});
it('should handle query parameters', async () => {

View File

@@ -0,0 +1,131 @@
import { describe, it, expect, beforeAll, afterAll, vi } from 'vitest';
import { Hono } from 'hono';
import { serve } from '@hono/node-server';
import { startServers } from '../../src/server.js';
import fetch from 'node-fetch';
import { OpenAPIV3_1 } from 'openapi-types';
import { openApiStore } from '../../src/store/openApiStore.js';
import express from 'express';
import { Server } from 'http';
import bodyParser from 'body-parser';
// Create a mock version of startServers function that operates on our test ports
// This function is no longer needed since we're using the real startServers
// function createMockServer(targetUrl: string, port: number): Server {
// // ... existing code ...
// }
describe('Server Integration Tests', () => {
const TARGET_PORT = 3000;
const PROXY_PORT = 3005; // Changed to avoid conflicts with other tests
const DOCS_PORT = 3006; // Changed to avoid conflicts with other tests
const TARGET_URL = `http://localhost:${TARGET_PORT}`;
const PROXY_URL = `http://localhost:${PROXY_PORT}`;
const DOCS_URL = `http://localhost:${DOCS_PORT}`;
let targetServer: any;
let proxyServer: Server;
let docsServer: Server;
beforeAll(async () => {
// Create a mock target API server
const targetApp = new Hono();
// Basic GET endpoint
targetApp.get('/api/test', (c) => {
return c.json({ message: 'Test successful' });
});
// POST endpoint for users
targetApp.post('/api/users', async (c) => {
try {
const body = await c.req.json();
c.status(201);
return c.json({ id: 1, ...body });
} catch (e) {
c.status(400);
return c.json({ error: 'Invalid JSON', message: (e as Error).message });
}
});
// Start the target server
targetServer = serve({ port: TARGET_PORT, fetch: targetApp.fetch });
// Clear the OpenAPI store
openApiStore.clear();
// Start the real proxy and docs servers
const servers = await startServers({
target: TARGET_URL,
proxyPort: PROXY_PORT,
docsPort: DOCS_PORT,
verbose: false
});
proxyServer = servers.proxyServer;
docsServer = servers.docsServer;
});
afterAll(async () => {
// Shutdown servers
targetServer?.close();
proxyServer?.close();
docsServer?.close();
});
it('should respond to GET requests and record them', async () => {
const response = await fetch(`${PROXY_URL}/api/test`);
expect(response.status).toBe(200);
const body = await response.json();
expect(body).toEqual({ message: 'Test successful' });
// Verify that the endpoint was recorded in OpenAPI spec
const specResponse = await fetch(`${DOCS_URL}/openapi.json`);
const spec = await specResponse.json() as OpenAPIV3_1.Document;
expect(spec.paths?.['/api/test']?.get).toBeDefined();
// Verify that the endpoint was recorded in HAR format
const harResponse = await fetch(`${DOCS_URL}/har`);
const har = await harResponse.json() as { log: { entries: any[] } };
expect(har.log.entries.length).toBeGreaterThan(0);
expect(har.log.entries).toContainEqual(
expect.objectContaining({
request: expect.objectContaining({
method: 'GET',
url: expect.stringContaining('/api/test')
})
})
);
});
it('should handle POST requests with JSON bodies', async () => {
const payload = { name: 'Test User', email: 'test@example.com' };
const response = await fetch(`${PROXY_URL}/api/users`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
});
expect(response.status).toBe(201);
const body = await response.json();
expect(body).toEqual({ id: 1, name: 'Test User', email: 'test@example.com' });
// Verify that the endpoint and request body were recorded
const specResponse = await fetch(`${DOCS_URL}/openapi.json`);
const spec = await specResponse.json() as OpenAPIV3_1.Document;
expect(spec.paths?.['/api/users']?.post?.requestBody).toBeDefined();
// Check that the request schema was generated
if (spec.paths?.['/api/users']?.post?.requestBody) {
const requestBody = spec.paths['/api/users'].post.requestBody as OpenAPIV3_1.RequestBodyObject;
if (requestBody.content) {
expect(requestBody.content['application/json']).toBeDefined();
expect(requestBody.content['application/json'].schema).toBeDefined();
}
}
});
});