Skip to content

Commit d0f8e1f

Browse files
committed
feat: align mock server runner parameters with proxy
1 parent fbc58eb commit d0f8e1f

File tree

8 files changed

+621
-499
lines changed

8 files changed

+621
-499
lines changed

mock-server/.eslintignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
build
2+
types

mock-server/.eslintrc.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
{
2+
"root": true,
3+
"env": {
4+
"node": true,
5+
"jest": true
6+
},
7+
"extends": [
8+
"eslint:recommended"
9+
],
10+
"overrides": [
11+
{
12+
"files": [
13+
"*.ts"
14+
],
15+
"parser": "@typescript-eslint/parser",
16+
"plugins": [
17+
"@typescript-eslint"
18+
],
19+
"extends": [
20+
"eslint:recommended",
21+
"plugin:@typescript-eslint/eslint-recommended",
22+
"plugin:@typescript-eslint/recommended"
23+
],
24+
"rules": {
25+
"@typescript-eslint/no-explicit-any": 0,
26+
"@typescript-eslint/no-var-requires": 0,
27+
"@typescript-eslint/explicit-module-boundary-types": [
28+
"warn",
29+
{
30+
"allowArgumentsExplicitlyTypedAsAny": true
31+
}
32+
]
33+
}
34+
}
35+
]
36+
}
37+

mock-server/.prettierrc.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"trailingComma": "all",
3+
"semi": true,
4+
"singleQuote": true
5+
}

mock-server/package-lock.json

Lines changed: 421 additions & 375 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

mock-server/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,13 +54,15 @@
5454
"@types/swagger-parser": "4.0.3",
5555
"@types/wait-on": "5.3.1",
5656
"@types/which": "2.0.1",
57+
"@typescript-eslint/eslint-plugin": "5.41.0",
58+
"@typescript-eslint/parser": "5.41.0",
5759
"axios": "0.19.2",
5860
"depcheck": "1.4.3",
61+
"eslint": "7.32.0",
5962
"gts": "3.1.1",
6063
"jest": "27.5.1",
6164
"ts-jest": "27.1.5",
62-
"tslint": "6.1.3",
63-
"typescript": "4.0.5",
65+
"typescript": "4.8.4",
6466
"wait-on": "6.0.1"
6567
}
6668
}

mock-server/src/app.ts

Lines changed: 120 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -5,125 +5,145 @@ import 'source-map-support/register';
55

66
import * as errorHandler from 'errorhandler';
77
import * as express from 'express';
8-
import {Request, Response} from 'express';
8+
import { Request, Response } from 'express';
99
import * as refParser from 'json-schema-ref-parser';
1010
import * as morgan from 'morgan';
11-
import OpenAPIBackend, {Request as OpenAPIRequest} from 'openapi-backend';
12-
import {OpenAPIV3} from 'openapi-types';
11+
import OpenAPIBackend, { Request as OpenAPIRequest } from 'openapi-backend';
12+
import { OpenAPIV3 } from 'openapi-types';
1313
import * as path from 'path';
1414
import * as http from 'http';
1515

16-
import {readJsonOrYamlSync} from './util';
16+
import { readJsonOrYamlSync } from './util';
1717

18-
export async function buildApp(apiDocFile: string):
19-
Promise<express.Application> {
20-
const apiDocRaw = readJsonOrYamlSync(apiDocFile);
21-
const apiDoc = await refParser.dereference(apiDocFile, apiDocRaw, {});
18+
export async function buildApp(
19+
apiDocFile: string,
20+
): Promise<express.Application> {
21+
const apiDocRaw = readJsonOrYamlSync(apiDocFile);
22+
const apiDoc = await refParser.dereference(apiDocFile, apiDocRaw, {});
2223

23-
const api = buildApi(apiDocFile, apiDoc);
24-
await api.init();
25-
debug(`Loaded API definition for ${path.basename(apiDocFile)} ("${
26-
api.document.info.title}", version: ${api.document.info.version})`);
24+
const api = buildApi(apiDocFile, apiDoc);
25+
await api.init();
26+
debug(
27+
`Loaded API definition for ${path.basename(apiDocFile)} ("${
28+
api.document.info.title
29+
}", version: ${api.document.info.version})`,
30+
);
2731

28-
return buildExpressApp(api);
32+
return buildExpressApp(api);
2933
}
3034

3135
/** Configures and build the OpenAPIBackend express middleware. */
3236
export function buildApi(apiDocFile: string, apiDoc: any): OpenAPIBackend {
33-
return new OpenAPIBackend({
34-
definition: apiDoc as OpenAPIV3.Document,
35-
strict: true,
36-
validate: false,
37-
ajvOpts: {unknownFormats: ['int32', 'int64', 'float', 'double']},
38-
handlers: {
39-
validationFail: async (c, _req: Request, res: Response) => {
40-
if (!c) return;
41-
res.setHeader('openapi-cop-openapi-file', apiDocFile);
42-
res.status(400).json({validation: c.validation});
43-
},
44-
notFound: async (c, _req: Request, res: Response) => {
45-
if (!c) return;
46-
// c.operationId is not defined, but c.operation is
47-
if (c.operation) {
48-
debug(
49-
'Cannot mock operation without an "operationId". Responding with 404.');
50-
}
51-
res.setHeader('openapi-cop-openapi-file', apiDocFile);
52-
res.status(404).json({error: 'not found'});
53-
},
54-
notImplemented: async (c, _req: Request, res: Response) => {
55-
if (!c) return;
56-
res.setHeader('openapi-cop-openapi-file', apiDocFile);
57-
58-
if (!c.operation || !c.operation.operationId) {
59-
debug('Cannot mock operation without an "operationId"');
60-
return res.status(404).json({error: 'not found'});
61-
}
62-
const {status, mock} =
63-
c.api.mockResponseForOperation(c.operation.operationId);
64-
65-
return res.status(status).json(mock);
66-
}
67-
}
68-
});
37+
return new OpenAPIBackend({
38+
definition: apiDoc as OpenAPIV3.Document,
39+
strict: true,
40+
validate: false,
41+
ajvOpts: { unknownFormats: ['int32', 'int64', 'float', 'double'] },
42+
handlers: {
43+
validationFail: async (c, _req: Request, res: Response) => {
44+
if (!c) return;
45+
res.setHeader('openapi-cop-openapi-file', apiDocFile);
46+
res.status(400).json({ validation: c.validation });
47+
},
48+
notFound: async (c, _req: Request, res: Response) => {
49+
if (!c) return;
50+
// c.operationId is not defined, but c.operation is
51+
if (c.operation) {
52+
debug(
53+
'Cannot mock operation without an "operationId". Responding with 404.',
54+
);
55+
}
56+
res.setHeader('openapi-cop-openapi-file', apiDocFile);
57+
res.status(404).json({ error: 'not found' });
58+
},
59+
notImplemented: async (c, _req: Request, res: Response) => {
60+
if (!c) return;
61+
res.setHeader('openapi-cop-openapi-file', apiDocFile);
62+
63+
if (!c.operation || !c.operation.operationId) {
64+
debug('Cannot mock operation without an "operationId"');
65+
return res.status(404).json({ error: 'not found' });
66+
}
67+
const { status, mock } = c.api.mockResponseForOperation(
68+
c.operation.operationId,
69+
);
70+
71+
return res.status(status).json(mock);
72+
},
73+
},
74+
});
6975
}
7076

7177
/**
7278
* Creates an express app and attaches a OpenAPIBackend middleware instance to
7379
* it.
7480
*/
75-
export async function buildExpressApp(api: OpenAPIBackend):
76-
Promise<express.Application> {
77-
const app: express.Application = express();
78-
app.use(express.json());
79-
80-
if (debugMod.enabled('openapi-cop:mock')) {
81-
// Logging of the form "openapi-cop:mock METHOD /url 123B (50ms)"
82-
app.use(morgan((tokens, req, res) => {
83-
return [
84-
chalk.bold(' openapi-cop:mock'), tokens.method(req, res),
85-
tokens.url(req, res), tokens.status(req, res),
86-
tokens.res(req, res, 'content-length') + 'B',
87-
'(' + tokens['response-time'](req, res), 'ms)'
88-
].join(' ');
89-
}));
90-
}
91-
92-
// Attach OpenAPI backend
93-
app.use(
94-
(req, res) => api.handleRequest(req as OpenAPIRequest, req, res));
95-
96-
app.use(
97-
// tslint:disable-next-line:no-any
98-
(err: any, _req: Request, res: Response) => {
99-
console.error(err.stack);
100-
res.status(500).send('Server error');
101-
});
102-
103-
// Display full error stack traces
104-
if (process.env.NODE_ENV === 'development') {
105-
app.use(errorHandler());
106-
}
107-
108-
return app;
81+
export async function buildExpressApp(
82+
api: OpenAPIBackend,
83+
): Promise<express.Application> {
84+
const app: express.Application = express();
85+
app.use(express.json());
86+
87+
if (debugMod.enabled('openapi-cop:mock')) {
88+
// Logging of the form "openapi-cop:mock METHOD /url 123B (50ms)"
89+
app.use(
90+
morgan((tokens, req, res) => {
91+
return [
92+
chalk.bold(' openapi-cop:mock'),
93+
tokens.method(req, res),
94+
tokens.url(req, res),
95+
tokens.status(req, res),
96+
tokens.res(req, res, 'content-length') + 'B',
97+
'(' + tokens['response-time'](req, res),
98+
'ms)',
99+
].join(' ');
100+
}),
101+
);
102+
}
103+
104+
// Attach OpenAPI backend
105+
app.use((req, res) => api.handleRequest(req as OpenAPIRequest, req, res));
106+
107+
app.use(
108+
// tslint:disable-next-line:no-any
109+
(err: any, _req: Request, res: Response) => {
110+
console.error(err.stack);
111+
res.status(500).send('Server error');
112+
},
113+
);
114+
115+
// Display full error stack traces
116+
if (process.env.NODE_ENV === 'development') {
117+
app.use(errorHandler());
118+
}
119+
120+
return app;
121+
}
122+
123+
export type MockOptions = BaseMockOptions & ExtendedMockOptions;
124+
125+
export interface BaseMockOptions {
126+
port: string | number;
127+
}
128+
129+
export interface ExtendedMockOptions {
130+
apiDocFile: string;
109131
}
110132

111133
/** Builds the app and runs it on the given port. */
112-
export async function runApp(
113-
port: string|number, apiDocFile: string): Promise<http.Server> {
114-
try {
115-
const app = await buildApp(apiDocFile);
116-
let server: http.Server;
117-
return new Promise<http.Server>(resolve => {
118-
server = app.listen(port, () => {
119-
resolve();
120-
});
121-
})
122-
.then(() => {
123-
return server;
124-
});
125-
} catch (e) {
126-
console.error(`Failed to run mock server:\n${e.message}`);
127-
return Promise.reject();
128-
}
134+
export async function runApp({ port, apiDocFile }: MockOptions): Promise<http.Server> {
135+
try {
136+
const app = await buildApp(apiDocFile);
137+
let server: http.Server;
138+
return new Promise<http.Server>((resolve) => {
139+
server = app.listen(port, () => {
140+
resolve(server);
141+
});
142+
}).then(() => {
143+
return server;
144+
});
145+
} catch (error) {
146+
console.error('Failed to run mock server', error);
147+
return Promise.reject();
148+
}
129149
}

mock-server/src/cli.ts

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,28 @@
11
#!/usr/bin/env node
22
const debugMod = require('debug');
33
const debug = debugMod('openapi-cop:mock');
4-
debug.log = console.log.bind(console); // output to stdout
4+
debug.log = console.log.bind(console); // output to stdout
55
import chalk = require('chalk');
66
import * as chokidar from 'chokidar';
77
import * as program from 'commander';
88
import * as http from 'http';
99
import * as path from 'path';
1010

11-
import {runApp} from './app';
11+
import { runApp } from './app';
1212

1313
// Rename for consistent display of package name in help output
1414
process.argv[1] = path.join(process.argv[1], 'openapi-cop-mock-server');
1515

16-
program //
17-
.option('-f, --file <file>', 'path to the OpenAPI document')
18-
.option('-p, --port <port>', 'port number on which to run server', 8889)
19-
.option(
20-
'-w, --watch [watchLocation]',
21-
'watch for changes in a file/directory (falls back to the OpenAPI file) and restart server accordingly')
22-
.option('-v, --verbose', 'show verbose output')
23-
.version('0.0.1')
24-
.parse(process.argv);
16+
program //
17+
.option('-f, --file <file>', 'path to the OpenAPI document')
18+
.option('-p, --port <port>', 'port number on which to run server', 8889)
19+
.option(
20+
'-w, --watch [watchLocation]',
21+
'watch for changes in a file/directory (falls back to the OpenAPI file) and restart server accordingly',
22+
)
23+
.option('-v, --verbose', 'show verbose output')
24+
.version('0.0.1')
25+
.parse(process.argv);
2526

2627
let server: http.Server;
2728

@@ -43,15 +44,17 @@ if (!program.file) {
4344
async function start(restart = false) {
4445
const apiDocFile = path.resolve(program.file);
4546
try {
46-
server = await runApp(program.port, apiDocFile);
47+
server = await runApp({ port: program.port, apiDocFile });
4748
} catch (e) {
4849
process.exit();
4950
}
5051

5152
if (!restart) {
5253
console.log(
53-
chalk.blue(`openapi-cop mock server is running at http://localhost:${
54-
program.port}`));
54+
chalk.blue(
55+
`openapi-cop mock server is running at http://localhost:${program.port}`,
56+
),
57+
);
5558
} else {
5659
console.log(chalk.hex('#eeeeee')('Restarted mock server'));
5760
}
@@ -63,13 +66,16 @@ start();
6366
// Watch for file changes and restart server
6467
if (program.watch) {
6568
const watchLocation =
66-
typeof program.watch !== 'boolean' ? program.watch : program.file;
67-
const watcher = chokidar.watch(watchLocation, {persistent: true});
69+
typeof program.watch !== 'boolean' ? program.watch : program.file;
70+
const watcher = chokidar.watch(watchLocation, { persistent: true });
6871
console.log(chalk.blue(`Watching changes in '${watchLocation}'`));
6972

70-
watcher.on('change', path => {
71-
console.log(chalk.hex('#eeeeee')(
72-
`Detected change in file ${path}. Restarting server...`));
73+
watcher.on('change', (path) => {
74+
console.log(
75+
chalk.hex('#eeeeee')(
76+
`Detected change in file ${path}. Restarting server...`,
77+
),
78+
);
7379
server.close(() => {
7480
start(true);
7581
});

0 commit comments

Comments
 (0)