Skip to content

Commit 8555a9e

Browse files
committed
feat: Print OpenAPI docs for mocks
1 parent 80a52d5 commit 8555a9e

File tree

5 files changed

+156
-5
lines changed

5 files changed

+156
-5
lines changed

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { type AxiosInstance } from 'axios';
22
import Proxy from './runtime/proxy';
33

4-
export function defineProxy(apiInstance: AxiosInstance) {
5-
return new Proxy(apiInstance);
4+
export function defineProxy(apiInstance: AxiosInstance, generateDocs = false) {
5+
return new Proxy(apiInstance, generateDocs);
66
}

src/runtime/docGenerator.ts

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
interface MockResponse {
2+
status: number;
3+
body: any;
4+
}
5+
6+
interface MockEndpoint {
7+
method: string;
8+
path: string;
9+
response: MockResponse;
10+
}
11+
12+
interface OpenAPI {
13+
openapi: string;
14+
info: {
15+
title: string;
16+
version: string;
17+
};
18+
paths: Record<string, any>;
19+
}
20+
21+
export class DocGenerator {
22+
private mocks: MockEndpoint[] = [];
23+
24+
private filename: string;
25+
26+
constructor(filename = 'openapi.json') {
27+
this.filename = filename;
28+
}
29+
30+
/**
31+
* Adiciona um novo mock e automaticamente atualiza o JSON de documentação
32+
* @param method - Método HTTP (GET, POST, etc.)
33+
* @param path - Endpoint da API
34+
* @param status - Código de status HTTP da resposta
35+
* @param body - Corpo da resposta
36+
*/
37+
public addMock(
38+
method: string,
39+
path: string,
40+
status: number,
41+
body: any,
42+
): void {
43+
// Verifica se já existe um mock com o mesmo método e path para evitar duplicação
44+
const existingMockIndex = this.mocks.findIndex(
45+
mock => mock.method === method.toUpperCase() && mock.path === path,
46+
);
47+
48+
if (existingMockIndex !== -1) {
49+
// Se já existe, atualiza o mock existente
50+
this.mocks[existingMockIndex] = {
51+
method: method.toUpperCase(),
52+
path,
53+
response: { status, body },
54+
};
55+
} else {
56+
// Caso contrário, adiciona um novo mock
57+
this.mocks.push({
58+
method: method.toUpperCase(),
59+
path,
60+
response: { status, body },
61+
});
62+
}
63+
64+
// Regenera a documentação automaticamente
65+
this.generateOpenApiJson();
66+
}
67+
68+
/**
69+
* Gera o JSON de documentação OpenAPI e salva no arquivo definido no construtor
70+
*/
71+
private generateOpenApiJson(): void {
72+
const openAPI: OpenAPI = {
73+
openapi: '3.0.0',
74+
info: {
75+
title: 'Mocked API',
76+
version: '1.0.0',
77+
},
78+
paths: {},
79+
};
80+
81+
// Construindo os endpoints no formato OpenAPI
82+
this.mocks.forEach(({ method, path, response }) => {
83+
if (!openAPI.paths[path]) {
84+
openAPI.paths[path] = {};
85+
}
86+
87+
openAPI.paths[path][method.toLowerCase()] = {
88+
summary: `Mock response for ${method} ${path}`,
89+
responses: {
90+
[response.status]: {
91+
description: `Mock response for ${method} ${path}`,
92+
content: {
93+
'application/json': response.body,
94+
},
95+
},
96+
},
97+
};
98+
});
99+
100+
console.log('\n\n## OpenAPI JSON ##');
101+
console.log(JSON.stringify(openAPI, null, 2));
102+
console.log('\n\n');
103+
}
104+
}

src/runtime/handler.ts

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
ResponseChanger,
55
RouteConfig,
66
} from '../types';
7+
import { DocGenerator } from './docGenerator';
78
import {
89
ejectFromRequest,
910
ejectFromResponse,
@@ -21,16 +22,20 @@ export default class Handler {
2122

2223
params?: object;
2324

25+
docGenerator?: DocGenerator;
26+
2427
constructor(
2528
scope: Proxy,
2629
verb: string,
2730
path: string | RegExp,
2831
params?: object,
32+
docGenerator?: DocGenerator,
2933
) {
3034
this.scope = scope;
3135
this.verb = verb;
3236
this.path = path;
3337
this.params = params;
38+
this.docGenerator = docGenerator;
3439
}
3540

3641
private setProxy(
@@ -71,7 +76,6 @@ export default class Handler {
7176
config,
7277
request: requestConfig,
7378
};
74-
7579
if (status < 400) resolve(response);
7680
else reject(response);
7781
});
@@ -139,11 +143,13 @@ export default class Handler {
139143

140144
reply(statusCodeOrConfig: number | RouteConfig, mock?: unknown) {
141145
this.setProxy(statusCodeOrConfig, mock);
146+
this.addMockDoc(statusCodeOrConfig, mock);
142147
return this.scope;
143148
}
144149

145150
replyOnce(statusCodeOrConfig: number | RouteConfig, mock?: unknown) {
146151
this.setProxy(statusCodeOrConfig, mock, true);
152+
this.addMockDoc(statusCodeOrConfig, mock);
147153
return this.scope;
148154
}
149155

@@ -176,4 +182,21 @@ export default class Handler {
176182
this.setChangerResponseData<T>(changer, true);
177183
return this;
178184
}
185+
186+
addMockDoc(statusCodeOrFunction: number | RouteConfig, mock?: unknown) {
187+
if (typeof statusCodeOrFunction === 'function') {
188+
Promise.resolve(statusCodeOrFunction()).then(result => {
189+
const [status, data] = result;
190+
this.docGenerator?.addMock(
191+
this.verb,
192+
this.path.toString(),
193+
status,
194+
data,
195+
);
196+
});
197+
return;
198+
}
199+
const status = statusCodeOrFunction;
200+
this.docGenerator?.addMock(this.verb, this.path.toString(), status, mock);
201+
}
179202
}

src/runtime/proxy.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { AxiosInstance } from 'axios';
2+
import { DocGenerator } from './docGenerator';
23
import Handler from './handler';
34
import { clearAll } from './helpers';
45

@@ -11,12 +12,15 @@ export default class Proxy {
1112

1213
params?: object;
1314

14-
constructor(axios: AxiosInstance) {
15+
docGenerator?: DocGenerator;
16+
17+
constructor(axios: AxiosInstance, generateDocs = false) {
1518
this.axios = axios;
19+
if (generateDocs) this.docGenerator = new DocGenerator();
1620
}
1721

1822
private setup(verb: string, path: string | RegExp, params?: object) {
19-
const handler = new Handler(this, verb, path, params);
23+
const handler = new Handler(this, verb, path, params, this.docGenerator);
2024
return handler;
2125
}
2226

test/index.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,5 +489,25 @@ describe('axios-dev-proxy tests', () => {
489489
expect(server.isDone()).toBe(true);
490490
expect(server2.isDone()).toBe(true);
491491
});
492+
493+
it('should print docs once', async () => {
494+
const documentedProxy = defineProxy(api, true);
495+
server
496+
.get('/generate-doc')
497+
.reply(200, { data: 1 })
498+
.get('/generate-doc-2')
499+
.reply(200, { data: 2 });
500+
const consoleLog = vi.spyOn(console, 'log');
501+
documentedProxy.onGet('/generate-doc').reply(200, {
502+
data: 'doc-1',
503+
});
504+
documentedProxy
505+
.onGet('/generate-doc-2')
506+
.reply(() => [201, { data: 'doc-2' }]);
507+
508+
await api.get('/generate-doc');
509+
await api.get('/generate-doc-2');
510+
expect(consoleLog).toBeCalled();
511+
});
492512
});
493513
});

0 commit comments

Comments
 (0)