Skip to content

Commit 57fa993

Browse files
committed
refactor
1 parent c0ec967 commit 57fa993

31 files changed

Lines changed: 1885 additions & 1702 deletions

.env.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
API_SOURCES=[{"name": "petstore", "url": "https://petstore.swagger.io/v2/swagger.json", "method": "GET", "type": "api"}]

example/api/api.test.ts

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import assert from 'node:assert';
2+
import { after, before, beforeEach, describe, it } from 'node:test';
3+
import { join } from 'path';
4+
import { CacheManager } from '../../build/utils/cache.js';
5+
import { SourceMethod, SourceType } from '../../build/utils/source.js';
6+
import { MockHttpServer } from './mock-server.js';
7+
import { clearCache, setupCacheWithSources } from './test-utils.js';
8+
9+
describe('CacheManager - OpenAPI File-based Sources', () => {
10+
const fixturesPath = join(process.cwd(), 'example', 'api', 'fixtures');
11+
12+
beforeEach(() => {
13+
clearCache();
14+
});
15+
16+
describe('OpenAPI JSON Schema File', () => {
17+
it('should load and cache OpenAPI schema from JSON file', async () => {
18+
await setupCacheWithSources([
19+
{
20+
name: 'TestOpenAPIJSON',
21+
type: SourceType.API,
22+
path: join(fixturesPath, 'json', 'openapi-schema.json')
23+
}
24+
]);
25+
26+
const docs = await CacheManager.getDocs('TestOpenAPIJSON');
27+
28+
assert.strictEqual(docs.length, 1, 'Should have one cache entry');
29+
assert.strictEqual(docs[0].name, 'TestOpenAPIJSON', 'Cache entry name should match');
30+
assert.ok(docs[0].resources.length > 0, 'Should have resources');
31+
32+
const getUsersEndpoint = docs[0].resources.find(r => r.name === 'GET /users');
33+
assert.ok(getUsersEndpoint, 'Should have GET /users endpoint');
34+
assert.strictEqual(getUsersEndpoint.type, 'GET', 'Should have correct resource type');
35+
assert.strictEqual(getUsersEndpoint.description, 'Get all users', 'Should have correct summary');
36+
37+
const postUsersEndpoint = docs[0].resources.find(r => r.name === 'POST /users');
38+
assert.ok(postUsersEndpoint, 'Should have POST /users endpoint');
39+
assert.strictEqual(postUsersEndpoint.type, 'POST', 'Should have correct resource type');
40+
41+
const getUserByIdEndpoint = docs[0].resources.find(r => r.name === 'GET /users/{id}');
42+
assert.ok(getUserByIdEndpoint, 'Should have GET /users/{id} endpoint');
43+
44+
const putUserEndpoint = docs[0].resources.find(r => r.name === 'PUT /users/{id}');
45+
assert.ok(putUserEndpoint, 'Should have PUT /users/{id} endpoint');
46+
47+
const deleteUserEndpoint = docs[0].resources.find(r => r.name === 'DELETE /users/{id}');
48+
assert.ok(deleteUserEndpoint, 'Should have DELETE /users/{id} endpoint');
49+
});
50+
51+
it('should retrieve specific endpoint details from OpenAPI JSON schema', async () => {
52+
await setupCacheWithSources([
53+
{
54+
name: 'TestOpenAPIJSON',
55+
type: SourceType.API,
56+
path: join(fixturesPath, 'json', 'openapi-schema.json')
57+
}
58+
]);
59+
60+
const details = await CacheManager.getDetails('POST /users');
61+
62+
assert.ok(details.length > 0, 'Should find POST /users endpoint details');
63+
assert.strictEqual(details[0].resources[0].name, 'POST /users', 'Should match endpoint name');
64+
65+
const postResource = details[0].resources[0];
66+
assert.ok(postResource.schema, 'Should have schema');
67+
assert.strictEqual(typeof postResource.schema, 'string', 'Schema should be a string');
68+
69+
const schema = JSON.parse(postResource.schema);
70+
assert.ok(schema.openapi, 'Should have OpenAPI version');
71+
assert.ok(schema.paths['/users'].post, 'Should have POST /users endpoint in schema');
72+
});
73+
});
74+
75+
describe('OpenAPI YAML Schema File', () => {
76+
it('should load and cache OpenAPI schema from YAML file', async () => {
77+
await setupCacheWithSources([
78+
{
79+
name: 'TestOpenAPIYAML',
80+
type: SourceType.API,
81+
path: join(fixturesPath, 'yaml', 'openapi-schema.yaml')
82+
}
83+
]);
84+
85+
const docs = await CacheManager.getDocs('TestOpenAPIYAML');
86+
87+
assert.strictEqual(docs.length, 1, 'Should have one cache entry');
88+
assert.strictEqual(docs[0].name, 'TestOpenAPIYAML', 'Cache entry name should match');
89+
assert.ok(docs[0].resources.length > 0, 'Should have resources');
90+
91+
const getUsersEndpoint = docs[0].resources.find(r => r.name === 'GET /users');
92+
assert.ok(getUsersEndpoint, 'Should have GET /users endpoint');
93+
94+
const postUsersEndpoint = docs[0].resources.find(r => r.name === 'POST /users');
95+
assert.ok(postUsersEndpoint, 'Should have POST /users endpoint');
96+
97+
const getUserByIdEndpoint = docs[0].resources.find(r => r.name === 'GET /users/{id}');
98+
assert.ok(getUserByIdEndpoint, 'Should have GET /users/{id} endpoint');
99+
});
100+
});
101+
});
102+
103+
describe('CacheManager - OpenAPI URL-based Sources', () => {
104+
let mockServer: MockHttpServer;
105+
const fixturesPath = join(process.cwd(), 'example', 'api', 'fixtures');
106+
const TEST_PORT = 8765;
107+
108+
beforeEach(() => {
109+
clearCache();
110+
});
111+
112+
before(async () => {
113+
mockServer = new MockHttpServer({
114+
port: TEST_PORT,
115+
openApiJsonPath: join(fixturesPath, 'json', 'openapi-schema.json'),
116+
openApiYamlPath: join(fixturesPath, 'yaml', 'openapi-schema.yaml')
117+
});
118+
await mockServer.start();
119+
});
120+
121+
after(async () => {
122+
await mockServer.stop();
123+
});
124+
125+
describe('OpenAPI URL Source - JSON', () => {
126+
it('should load and cache OpenAPI schema from JSON URL', async () => {
127+
await setupCacheWithSources([
128+
{
129+
name: 'TestOpenAPIURLJSON',
130+
type: SourceType.API,
131+
method: SourceMethod.GET,
132+
url: mockServer.getUrl('/openapi.json')
133+
}
134+
]);
135+
136+
const docs = await CacheManager.getDocs('TestOpenAPIURLJSON');
137+
138+
assert.strictEqual(docs.length, 1, 'Should have one cache entry');
139+
assert.strictEqual(docs[0].name, 'TestOpenAPIURLJSON', 'Cache entry name should match');
140+
assert.ok(docs[0].resources.length > 0, 'Should have resources');
141+
142+
const getUsersEndpoint = docs[0].resources.find(r => r.name === 'GET /users');
143+
assert.ok(getUsersEndpoint, 'Should have GET /users endpoint');
144+
145+
const postUsersEndpoint = docs[0].resources.find(r => r.name === 'POST /users');
146+
assert.ok(postUsersEndpoint, 'Should have POST /users endpoint');
147+
});
148+
});
149+
150+
describe('OpenAPI URL Source - YAML', () => {
151+
it('should load and cache OpenAPI schema from YAML URL', async () => {
152+
await setupCacheWithSources([
153+
{
154+
name: 'TestOpenAPIURLYAML',
155+
type: SourceType.API,
156+
method: SourceMethod.GET,
157+
url: mockServer.getUrl('/openapi.yaml')
158+
}
159+
]);
160+
161+
const docs = await CacheManager.getDocs('TestOpenAPIURLYAML');
162+
163+
assert.strictEqual(docs.length, 1, 'Should have one cache entry');
164+
assert.strictEqual(docs[0].name, 'TestOpenAPIURLYAML', 'Cache entry name should match');
165+
assert.ok(docs[0].resources.length > 0, 'Should have resources');
166+
167+
const getUsersEndpoint = docs[0].resources.find(r => r.name === 'GET /users');
168+
assert.ok(getUsersEndpoint, 'Should have GET /users endpoint');
169+
170+
const postUsersEndpoint = docs[0].resources.find(r => r.name === 'POST /users');
171+
assert.ok(postUsersEndpoint, 'Should have POST /users endpoint');
172+
});
173+
});
174+
});
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

example/api/mock-server.ts

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import SwaggerParser from '@apidevtools/swagger-parser';
2+
import { createServer, IncomingMessage, Server, ServerResponse } from 'http';
3+
4+
export interface MockServerConfig {
5+
port: number;
6+
openApiJsonPath?: string;
7+
openApiYamlPath?: string;
8+
}
9+
10+
export class MockHttpServer {
11+
private server: Server | null = null;
12+
private port: number;
13+
private openApiJsonPath?: string;
14+
private openApiYamlPath?: string;
15+
16+
constructor(config: MockServerConfig) {
17+
this.port = config.port;
18+
this.openApiJsonPath = config.openApiJsonPath;
19+
this.openApiYamlPath = config.openApiYamlPath;
20+
}
21+
22+
async start(): Promise<void> {
23+
return new Promise((resolve, reject) => {
24+
this.server = createServer((req: IncomingMessage, res: ServerResponse) => {
25+
this.handleRequest(req, res);
26+
});
27+
28+
this.server.on('error', reject);
29+
this.server.listen(this.port, () => {
30+
resolve();
31+
});
32+
});
33+
}
34+
35+
async stop(): Promise<void> {
36+
return new Promise((resolve, reject) => {
37+
if (!this.server) {
38+
resolve();
39+
return;
40+
}
41+
42+
this.server.close((err) => {
43+
if (err) {
44+
reject(err);
45+
} else {
46+
this.server = null;
47+
resolve();
48+
}
49+
});
50+
});
51+
}
52+
53+
getUrl(path: string): string {
54+
return `http://localhost:${this.port}${path}`;
55+
}
56+
57+
private handleRequest(req: IncomingMessage, res: ServerResponse): void {
58+
if (req.method === 'GET' && req.url === '/openapi.json') {
59+
this.handleOpenApiJson(res).catch(() => {
60+
res.writeHead(500, { 'Content-Type': 'application/json' });
61+
res.end(JSON.stringify({ error: 'Internal server error' }));
62+
});
63+
return;
64+
}
65+
66+
if (req.method === 'GET' && req.url === '/openapi.yaml') {
67+
this.handleOpenApiYaml(res).catch(() => {
68+
res.writeHead(500, { 'Content-Type': 'text/plain' });
69+
res.end('Internal server error');
70+
});
71+
return;
72+
}
73+
74+
res.writeHead(404, { 'Content-Type': 'application/json' });
75+
res.end(JSON.stringify({ error: 'Not found' }));
76+
}
77+
78+
private async handleOpenApiJson(res: ServerResponse): Promise<void> {
79+
if (!this.openApiJsonPath) {
80+
res.writeHead(500, { 'Content-Type': 'application/json' });
81+
res.end(JSON.stringify({ error: 'OpenAPI JSON schema not configured' }));
82+
return;
83+
}
84+
85+
try {
86+
const bundled = await SwaggerParser.bundle(this.openApiJsonPath);
87+
res.writeHead(200, { 'Content-Type': 'application/json' });
88+
res.end(JSON.stringify(bundled));
89+
} catch (error) {
90+
res.writeHead(500, { 'Content-Type': 'application/json' });
91+
res.end(JSON.stringify({ error: 'Failed to bundle OpenAPI schema' }));
92+
}
93+
}
94+
95+
private async handleOpenApiYaml(res: ServerResponse): Promise<void> {
96+
if (!this.openApiYamlPath) {
97+
res.writeHead(500, { 'Content-Type': 'text/plain' });
98+
res.end('OpenAPI YAML schema not configured');
99+
return;
100+
}
101+
102+
try {
103+
const bundled = await SwaggerParser.bundle(this.openApiYamlPath);
104+
res.writeHead(200, { 'Content-Type': 'application/json' });
105+
res.end(JSON.stringify(bundled));
106+
} catch (error) {
107+
res.writeHead(500, { 'Content-Type': 'text/plain' });
108+
res.end('Failed to bundle OpenAPI schema');
109+
}
110+
}
111+
}

example/api/test-utils.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { CacheManager } from '../../build/utils/cache.js';
2+
3+
export function clearCache() {
4+
CacheManager.clear();
5+
}
6+
7+
export async function setupCacheWithSources(sources: any[]) {
8+
clearCache();
9+
process.env.API_SOURCES = JSON.stringify(sources);
10+
await CacheManager.refresh();
11+
}

0 commit comments

Comments
 (0)