apx-toolkit
Version:
Automatically discover APIs and generate complete integration packages: code in 12 languages, TypeScript types, test suites, SDK packages, API documentation, mock servers, performance reports, and contract tests. Saves 2-4 weeks of work in seconds.
373 lines (325 loc) • 13.8 kB
text/typescript
/**
* Test Suite Generator
* Generates test suites for discovered APIs
* Makes the tool a developer's dream with ready-to-use tests
*/
import type { DiscoveredAPI } from '../types.js';
export type TestFramework = 'jest' | 'pytest' | 'mocha' | 'vitest' | 'playwright';
export interface TestSuite {
framework: TestFramework;
code: string;
filename: string;
description: string;
}
/**
* Generates test suites for an API in multiple frameworks
*/
export function generateTestSuites(api: DiscoveredAPI, baseUrl?: string): TestSuite[] {
const suites: TestSuite[] = [];
suites.push(generateJestTests(api, baseUrl));
suites.push(generatePytestTests(api, baseUrl));
suites.push(generateMochaTests(api, baseUrl));
suites.push(generateVitestTests(api, baseUrl));
suites.push(generatePlaywrightTests(api, baseUrl));
return suites;
}
function generateJestTests(api: DiscoveredAPI, baseUrl?: string): TestSuite {
const url = new URL(api.baseUrl);
const apiBaseUrl = baseUrl || `${url.protocol}//${url.host}`;
const pathName = url.pathname.replace(/[^a-zA-Z0-9]/g, '') || 'Api';
const testName = `${capitalize(pathName)}${api.method}Test`;
let code = `// Jest Test Suite\n`;
code += `// Auto-generated by APX\n\n`;
code += `describe('${api.method} ${url.pathname}', () => {\n`;
code += ` const baseUrl = '${apiBaseUrl}';\n`;
code += ` const endpoint = '${url.pathname}';\n\n`;
// Add headers setup
if (Object.keys(api.headers).length > 0) {
code += ` const headers = {\n`;
for (const [key, value] of Object.entries(api.headers)) {
code += ` '${key}': '${value}',\n`;
}
code += ` };\n\n`;
}
code += ` test('should return 200 status', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` expect(response.status).toBe(200);\n`;
code += ` expect(response.headers.get('content-type')).toContain('application/json');\n`;
code += ` });\n\n`;
code += ` test('should return valid JSON', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` const data = await response.json();\n`;
code += ` expect(data).toBeDefined();\n`;
code += ` expect(typeof data).toBe('object');\n`;
code += ` });\n\n`;
// Add schema validation test if we can infer schema
code += ` test('should match expected schema', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` const data = await response.json();\n`;
code += ` \n`;
code += ` // Basic schema validation\n`;
code += ` expect(data).toBeDefined();\n`;
if (api.dataPath) {
const pathParts = api.dataPath.split('.');
code += ` // Validate data path: ${api.dataPath}\n`;
if (pathParts.length > 0) {
code += ` expect(data.${pathParts[0]}).toBeDefined();\n`;
}
}
code += ` // Add more specific schema assertions based on your API response structure\n`;
code += ` });\n\n`;
if (api.paginationInfo) {
code += ` test('should handle pagination', async () => {\n`;
const paramName = api.paginationInfo.paramName || 'page';
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}?${paramName}=1\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` expect(response.status).toBe(200);\n`;
code += ` const data = await response.json();\n`;
code += ` expect(data).toBeDefined();\n`;
code += ` });\n`;
}
code += `});\n`;
return {
framework: 'jest',
code,
filename: `${testName}.test.js`,
description: 'Jest test suite with fetch API',
};
}
function generatePytestTests(api: DiscoveredAPI, baseUrl?: string): TestSuite {
const url = new URL(api.baseUrl);
const apiBaseUrl = baseUrl || `${url.protocol}//${url.host}`;
const pathName = url.pathname.replace(/[^a-zA-Z0-9]/g, '') || 'api';
const testName = `test_${pathName.toLowerCase()}_${api.method.toLowerCase()}`;
let code = `# Pytest Test Suite\n`;
code += `# Auto-generated by APX\n\n`;
code += `import pytest\n`;
code += `import requests\n\n`;
code += `BASE_URL = '${apiBaseUrl}'\n`;
code += `ENDPOINT = '${url.pathname}'\n\n`;
// Add headers
if (Object.keys(api.headers).length > 0) {
code += `HEADERS = {\n`;
for (const [key, value] of Object.entries(api.headers)) {
code += ` '${key}': '${value}',\n`;
}
code += `}\n\n`;
}
code += `def test_status_code():\n`;
code += ` """Test that API returns 200 status code"""\n`;
code += ` url = f"{BASE_URL}{ENDPOINT}"\n`;
if (Object.keys(api.headers).length > 0) {
code += ` response = requests.${api.method.toLowerCase()}(url, headers=HEADERS)\n`;
} else {
code += ` response = requests.${api.method.toLowerCase()}(url)\n`;
}
code += ` assert response.status_code == 200\n`;
code += ` assert 'application/json' in response.headers.get('content-type', '')\n\n`;
code += `def test_json_response():\n`;
code += ` """Test that API returns valid JSON"""\n`;
code += ` url = f"{BASE_URL}{ENDPOINT}"\n`;
if (Object.keys(api.headers).length > 0) {
code += ` response = requests.${api.method.toLowerCase()}(url, headers=HEADERS)\n`;
} else {
code += ` response = requests.${api.method.toLowerCase()}(url)\n`;
}
code += ` data = response.json()\n`;
code += ` assert data is not None\n`;
code += ` assert isinstance(data, dict)\n\n`;
if (api.paginationInfo) {
code += `def test_pagination():\n`;
code += ` """Test pagination support"""\n`;
const paramName = api.paginationInfo.paramName || 'page';
code += ` url = f"{BASE_URL}{ENDPOINT}?${paramName}=1"\n`;
if (Object.keys(api.headers).length > 0) {
code += ` response = requests.${api.method.toLowerCase()}(url, headers=HEADERS)\n`;
} else {
code += ` response = requests.${api.method.toLowerCase()}(url)\n`;
}
code += ` assert response.status_code == 200\n`;
code += ` data = response.json()\n`;
code += ` assert data is not None\n`;
}
return {
framework: 'pytest',
code,
filename: `${testName}.py`,
description: 'Pytest test suite with requests',
};
}
function generateMochaTests(api: DiscoveredAPI, baseUrl?: string): TestSuite {
const url = new URL(api.baseUrl);
const apiBaseUrl = baseUrl || `${url.protocol}//${url.host}`;
const pathName = url.pathname.replace(/[^a-zA-Z0-9]/g, '') || 'Api';
let code = `// Mocha Test Suite\n`;
code += `// Auto-generated by APX\n\n`;
code += `const { expect } = require('chai');\n`;
code += `const fetch = require('node-fetch');\n\n`;
code += `describe('${api.method} ${url.pathname}', () => {\n`;
code += ` const baseUrl = '${apiBaseUrl}';\n`;
code += ` const endpoint = '${url.pathname}';\n\n`;
if (Object.keys(api.headers).length > 0) {
code += ` const headers = {\n`;
for (const [key, value] of Object.entries(api.headers)) {
code += ` '${key}': '${value}',\n`;
}
code += ` };\n\n`;
}
code += ` it('should return 200 status', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` expect(response.status).to.equal(200);\n`;
code += ` });\n\n`;
code += ` it('should return valid JSON', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` const data = await response.json();\n`;
code += ` expect(data).to.be.an('object');\n`;
code += ` });\n`;
code += `});\n`;
return {
framework: 'mocha',
code,
filename: `${pathName}.test.js`,
description: 'Mocha test suite with chai assertions',
};
}
function generateVitestTests(api: DiscoveredAPI, baseUrl?: string): TestSuite {
const url = new URL(api.baseUrl);
const apiBaseUrl = baseUrl || `${url.protocol}//${url.host}`;
const pathName = url.pathname.replace(/[^a-zA-Z0-9]/g, '') || 'Api';
let code = `// Vitest Test Suite\n`;
code += `// Auto-generated by APX\n\n`;
code += `import { describe, it, expect } from 'vitest';\n\n`;
code += `describe('${api.method} ${url.pathname}', () => {\n`;
code += ` const baseUrl = '${apiBaseUrl}';\n`;
code += ` const endpoint = '${url.pathname}';\n\n`;
if (Object.keys(api.headers).length > 0) {
code += ` const headers = {\n`;
for (const [key, value] of Object.entries(api.headers)) {
code += ` '${key}': '${value}',\n`;
}
code += ` };\n\n`;
}
code += ` it('should return 200 status', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` expect(response.status).toBe(200);\n`;
code += ` });\n\n`;
code += ` it('should return valid JSON', async () => {\n`;
code += ` const response = await fetch(\`\${baseUrl}\${endpoint}\`, {\n`;
code += ` method: '${api.method}',\n`;
if (Object.keys(api.headers).length > 0) {
code += ` headers,\n`;
}
code += ` });\n\n`;
code += ` const data = await response.json();\n`;
code += ` expect(data).toBeDefined();\n`;
code += ` expect(typeof data).toBe('object');\n`;
code += ` });\n`;
code += `});\n`;
return {
framework: 'vitest',
code,
filename: `${pathName}.test.ts`,
description: 'Vitest test suite (TypeScript-ready)',
};
}
function generatePlaywrightTests(api: DiscoveredAPI, baseUrl?: string): TestSuite {
const url = new URL(api.baseUrl);
const apiBaseUrl = baseUrl || `${url.protocol}//${url.host}`;
const pathName = url.pathname.replace(/[^a-zA-Z0-9]/g, '') || 'Api';
let code = `// Playwright API Test Suite\n`;
code += `// Auto-generated by APX\n\n`;
code += `import { test, expect } from '@playwright/test';\n\n`;
code += `test.describe('${api.method} ${url.pathname}', () => {\n`;
code += ` const baseUrl = '${apiBaseUrl}';\n`;
code += ` const endpoint = '${url.pathname}';\n\n`;
code += ` test('should return 200 status', async ({ request }) => {\n`;
code += ` const response = await request.${api.method.toLowerCase()}(\`\${baseUrl}\${endpoint}\``;
if (Object.keys(api.headers).length > 0) {
code += `, {\n`;
code += ` headers: {\n`;
for (const [key, value] of Object.entries(api.headers)) {
code += ` '${key}': '${value}',\n`;
}
code += ` },\n`;
code += ` }`;
}
code += `);\n\n`;
code += ` expect(response.status()).toBe(200);\n`;
code += ` expect(response.headers()['content-type']).toContain('application/json');\n`;
code += ` });\n\n`;
code += ` test('should return valid JSON', async ({ request }) => {\n`;
code += ` const response = await request.${api.method.toLowerCase()}(\`\${baseUrl}\${endpoint}\``;
if (Object.keys(api.headers).length > 0) {
code += `, {\n`;
code += ` headers: {\n`;
for (const [key, value] of Object.entries(api.headers)) {
code += ` '${key}': '${value}',\n`;
}
code += ` },\n`;
code += ` }`;
}
code += `);\n\n`;
code += ` const data = await response.json();\n`;
code += ` expect(data).toBeDefined();\n`;
code += ` expect(typeof data).toBe('object');\n`;
code += ` });\n`;
code += `});\n`;
return {
framework: 'playwright',
code,
filename: `${pathName}.spec.ts`,
description: 'Playwright API test suite',
};
}
/**
* Generates test suites for all discovered APIs
*/
export function generateAllTestSuites(
apis: DiscoveredAPI[],
baseUrl?: string
): Record<string, TestSuite[]> {
const result: Record<string, TestSuite[]> = {};
for (const api of apis) {
const url = new URL(api.baseUrl);
const key = `${api.method}_${url.pathname}`;
result[key] = generateTestSuites(api, baseUrl);
}
return result;
}
function capitalize(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}