@typespec/spector 0.1.0-alpha.2-dev.2 → 0.1.0-alpha.20-dev.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +131 -1
- package/README.md +37 -0
- package/dist/generated-defs/TypeSpec.Spector.ts-test.js +5 -2
- package/dist/generated-defs/TypeSpec.Spector.ts-test.js.map +1 -1
- package/dist/src/actions/helper.d.ts +8 -15
- package/dist/src/actions/helper.d.ts.map +1 -1
- package/dist/src/actions/helper.js +61 -69
- package/dist/src/actions/helper.js.map +1 -1
- package/dist/src/actions/serve.d.ts.map +1 -1
- package/dist/src/actions/serve.js +3 -4
- package/dist/src/actions/serve.js.map +1 -1
- package/dist/src/actions/server-test.d.ts +3 -5
- package/dist/src/actions/server-test.d.ts.map +1 -1
- package/dist/src/actions/server-test.js +108 -132
- package/dist/src/actions/server-test.js.map +1 -1
- package/dist/src/actions/upload-coverage-report.d.ts +2 -1
- package/dist/src/actions/upload-coverage-report.d.ts.map +1 -1
- package/dist/src/actions/upload-coverage-report.js +6 -2
- package/dist/src/actions/upload-coverage-report.js.map +1 -1
- package/dist/src/actions/upload-scenario-manifest.d.ts +4 -2
- package/dist/src/actions/upload-scenario-manifest.d.ts.map +1 -1
- package/dist/src/actions/upload-scenario-manifest.js +17 -7
- package/dist/src/actions/upload-scenario-manifest.js.map +1 -1
- package/dist/src/actions/validate-mock-apis.js +1 -1
- package/dist/src/actions/validate-mock-apis.js.map +1 -1
- package/dist/src/app/app.d.ts +1 -0
- package/dist/src/app/app.d.ts.map +1 -1
- package/dist/src/app/app.js +48 -54
- package/dist/src/app/app.js.map +1 -1
- package/dist/src/app/request-processor.d.ts +2 -2
- package/dist/src/app/request-processor.d.ts.map +1 -1
- package/dist/src/app/request-processor.js +10 -6
- package/dist/src/app/request-processor.js.map +1 -1
- package/dist/src/cli/cli.js +33 -21
- package/dist/src/cli/cli.js.map +1 -1
- package/dist/src/coverage/common.d.ts.map +1 -1
- package/dist/src/coverage/common.js +1 -0
- package/dist/src/coverage/common.js.map +1 -1
- package/dist/src/coverage/coverage-tracker.d.ts.map +1 -1
- package/dist/src/coverage/coverage-tracker.js.map +1 -1
- package/dist/src/coverage/scenario-manifest.d.ts +2 -2
- package/dist/src/coverage/scenario-manifest.d.ts.map +1 -1
- package/dist/src/coverage/scenario-manifest.js +4 -5
- package/dist/src/coverage/scenario-manifest.js.map +1 -1
- package/dist/src/lib/decorators.d.ts.map +1 -1
- package/dist/src/lib/decorators.js +0 -2
- package/dist/src/lib/decorators.js.map +1 -1
- package/dist/src/logger.d.ts +16 -2
- package/dist/src/logger.d.ts.map +1 -1
- package/dist/src/logger.js +27 -8
- package/dist/src/logger.js.map +1 -1
- package/dist/src/server/server.d.ts +1 -1
- package/dist/src/server/server.d.ts.map +1 -1
- package/dist/src/server/server.js +18 -5
- package/dist/src/server/server.js.map +1 -1
- package/dist/src/utils/body-utils.d.ts.map +1 -1
- package/dist/src/utils/request-utils.d.ts.map +1 -1
- package/docs/decorators.md +24 -0
- package/docs/using-spector.md +99 -0
- package/docs/writing-mock-apis.md +116 -0
- package/docs/writing-scenario-spec.md +49 -0
- package/generated-defs/TypeSpec.Spector.ts-test.ts +7 -2
- package/lib/main.tsp +1 -1
- package/package.json +23 -28
- package/src/actions/helper.ts +79 -95
- package/src/actions/serve.ts +3 -4
- package/src/actions/server-test.ts +132 -156
- package/src/actions/upload-coverage-report.ts +7 -1
- package/src/actions/upload-scenario-manifest.ts +22 -9
- package/src/actions/validate-mock-apis.ts +1 -1
- package/src/app/app.ts +71 -72
- package/src/app/request-processor.ts +16 -4
- package/src/cli/cli.ts +33 -21
- package/src/coverage/common.ts +1 -0
- package/src/coverage/coverage-tracker.ts +2 -2
- package/src/coverage/scenario-manifest.ts +5 -3
- package/src/lib/decorators.ts +0 -2
- package/src/logger.ts +39 -8
- package/src/scenarios-resolver.ts +1 -1
- package/src/server/server.ts +21 -7
- package/temp/.tsbuildinfo +1 -1
package/src/app/app.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
expandDyns,
|
|
3
|
+
MockApiDefinition,
|
|
4
|
+
MockBody,
|
|
5
|
+
MockMultipartBody,
|
|
6
|
+
MockRequest,
|
|
7
|
+
RequestExt,
|
|
8
|
+
ResolverConfig,
|
|
9
|
+
ScenarioMockApi,
|
|
10
|
+
} from "@typespec/spec-api";
|
|
2
11
|
import { ScenariosMetadata } from "@typespec/spec-coverage-sdk";
|
|
3
12
|
import { Response, Router } from "express";
|
|
4
13
|
import { getScenarioMetadata } from "../coverage/common.js";
|
|
@@ -19,6 +28,7 @@ export class MockApiApp {
|
|
|
19
28
|
private router = Router();
|
|
20
29
|
private server: MockApiServer;
|
|
21
30
|
private coverageTracker: CoverageTracker;
|
|
31
|
+
private resolverConfig!: ResolverConfig;
|
|
22
32
|
|
|
23
33
|
constructor(private config: ApiMockAppConfig) {
|
|
24
34
|
this.server = new MockApiServer({ port: config.port });
|
|
@@ -54,79 +64,77 @@ export class MockApiApp {
|
|
|
54
64
|
});
|
|
55
65
|
|
|
56
66
|
this.server.use("/", this.router);
|
|
57
|
-
|
|
67
|
+
// Getting the resolved port as setting 0 in the config will have express resolve on of the available ports
|
|
68
|
+
const port = await this.server.start();
|
|
69
|
+
this.resolverConfig = {
|
|
70
|
+
baseUrl: `http://localhost:${port}`,
|
|
71
|
+
};
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
private registerScenario(name: string, scenario: ScenarioMockApi) {
|
|
61
75
|
for (const endpoint of scenario.apis) {
|
|
62
|
-
if (endpoint.
|
|
63
|
-
|
|
64
|
-
processRequest(
|
|
65
|
-
this.coverageTracker,
|
|
66
|
-
name,
|
|
67
|
-
endpoint.uri,
|
|
68
|
-
req,
|
|
69
|
-
res,
|
|
70
|
-
endpoint.handler,
|
|
71
|
-
).catch((e) => {
|
|
72
|
-
logger.error("Unexpected request error", e);
|
|
73
|
-
res.status(500).end();
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
} else {
|
|
77
|
-
if (!endpoint.handler) {
|
|
78
|
-
endpoint.handler = createHandler(endpoint);
|
|
79
|
-
}
|
|
80
|
-
this.router.route(endpoint.uri)[endpoint.method]((req: RequestExt, res: Response) => {
|
|
81
|
-
processRequest(
|
|
82
|
-
this.coverageTracker,
|
|
83
|
-
name,
|
|
84
|
-
endpoint.uri,
|
|
85
|
-
req,
|
|
86
|
-
res,
|
|
87
|
-
endpoint.handler!,
|
|
88
|
-
).catch((e) => {
|
|
89
|
-
logger.error("Unexpected request error", e);
|
|
90
|
-
res.status(500).end();
|
|
91
|
-
});
|
|
92
|
-
});
|
|
76
|
+
if (!endpoint.handler) {
|
|
77
|
+
endpoint.handler = createHandler(endpoint, this.resolverConfig);
|
|
93
78
|
}
|
|
79
|
+
this.router.route(endpoint.uri)[endpoint.method]((req: RequestExt, res: Response) => {
|
|
80
|
+
processRequest(
|
|
81
|
+
this.coverageTracker,
|
|
82
|
+
name,
|
|
83
|
+
endpoint.uri,
|
|
84
|
+
req,
|
|
85
|
+
res,
|
|
86
|
+
endpoint.handler!,
|
|
87
|
+
this.resolverConfig,
|
|
88
|
+
).catch((e) => {
|
|
89
|
+
logger.error("Unexpected request error", e);
|
|
90
|
+
res.status(500).end();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
94
93
|
}
|
|
95
94
|
}
|
|
96
95
|
}
|
|
97
96
|
|
|
98
|
-
function
|
|
99
|
-
|
|
97
|
+
function validateBody(
|
|
98
|
+
req: MockRequest,
|
|
99
|
+
body: MockBody | MockMultipartBody,
|
|
100
|
+
config: ResolverConfig,
|
|
101
|
+
) {
|
|
102
|
+
if ("kind" in body) {
|
|
103
|
+
// custom handler for now.
|
|
104
|
+
} else {
|
|
105
|
+
if (Buffer.isBuffer(body.rawContent)) {
|
|
106
|
+
req.expect.rawBodyEquals(body.rawContent);
|
|
107
|
+
} else {
|
|
108
|
+
const raw =
|
|
109
|
+
typeof body.rawContent === "string" ? body.rawContent : body.rawContent?.serialize(config);
|
|
110
|
+
switch (body.contentType) {
|
|
111
|
+
case "application/json":
|
|
112
|
+
req.expect.coercedBodyEquals(JSON.parse(raw as any));
|
|
113
|
+
break;
|
|
114
|
+
case "application/xml":
|
|
115
|
+
req.expect.xmlBodyEquals(
|
|
116
|
+
(raw as any).replace(`<?xml version='1.0' encoding='UTF-8'?>`, ""),
|
|
117
|
+
);
|
|
118
|
+
break;
|
|
119
|
+
default:
|
|
120
|
+
req.expect.rawBodyEquals(raw);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
100
124
|
}
|
|
101
125
|
|
|
102
|
-
function createHandler(apiDefinition: MockApiDefinition) {
|
|
126
|
+
function createHandler(apiDefinition: MockApiDefinition, config: ResolverConfig) {
|
|
103
127
|
return (req: MockRequest) => {
|
|
128
|
+
const body = apiDefinition.request?.body;
|
|
104
129
|
// Validate body if present in the request
|
|
105
|
-
if (
|
|
106
|
-
|
|
107
|
-
apiDefinition.request.headers &&
|
|
108
|
-
apiDefinition.request.headers["Content-Type"] === "application/xml"
|
|
109
|
-
) {
|
|
110
|
-
req.expect.xmlBodyEquals(
|
|
111
|
-
apiDefinition.request.body.rawContent.replace(
|
|
112
|
-
`<?xml version='1.0' encoding='UTF-8'?>`,
|
|
113
|
-
"",
|
|
114
|
-
),
|
|
115
|
-
);
|
|
116
|
-
} else {
|
|
117
|
-
if (isObject(apiDefinition.request.body)) {
|
|
118
|
-
Object.entries(apiDefinition.request.body).forEach(([key, value]) => {
|
|
119
|
-
req.expect.deepEqual(req.body[key], value);
|
|
120
|
-
});
|
|
121
|
-
} else {
|
|
122
|
-
req.expect.coercedBodyEquals(apiDefinition.request.body);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
130
|
+
if (body) {
|
|
131
|
+
validateBody(req, body, config);
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
// Validate headers if present in the request
|
|
128
|
-
if (apiDefinition.request
|
|
129
|
-
|
|
135
|
+
if (apiDefinition.request?.headers) {
|
|
136
|
+
const headers = expandDyns(apiDefinition.request.headers, config);
|
|
137
|
+
Object.entries(headers).forEach(([key, value]) => {
|
|
130
138
|
if (key.toLowerCase() !== "content-type") {
|
|
131
139
|
if (Array.isArray(value)) {
|
|
132
140
|
req.expect.deepEqual(req.headers[key], value);
|
|
@@ -137,21 +145,12 @@ function createHandler(apiDefinition: MockApiDefinition) {
|
|
|
137
145
|
});
|
|
138
146
|
}
|
|
139
147
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (Array.isArray(value)) {
|
|
145
|
-
req.expect.deepEqual(req.params[key], value);
|
|
146
|
-
} else {
|
|
147
|
-
req.expect.deepEqual(req.params[key], String(value));
|
|
148
|
-
}
|
|
148
|
+
if (apiDefinition.request?.query) {
|
|
149
|
+
Object.entries(apiDefinition.request.query).forEach(([key, value]) => {
|
|
150
|
+
if (Array.isArray(value)) {
|
|
151
|
+
req.expect.deepEqual(req.query[key], value);
|
|
149
152
|
} else {
|
|
150
|
-
|
|
151
|
-
req.expect.deepEqual(req.query[key], value);
|
|
152
|
-
} else {
|
|
153
|
-
req.expect.containsQueryParam(key, String(value));
|
|
154
|
-
}
|
|
153
|
+
req.expect.containsQueryParam(key, String(value));
|
|
155
154
|
}
|
|
156
155
|
});
|
|
157
156
|
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
+
expandDyns,
|
|
2
3
|
MockRequest,
|
|
3
4
|
MockRequestHandler,
|
|
4
5
|
MockResponse,
|
|
5
6
|
RequestExt,
|
|
7
|
+
ResolverConfig,
|
|
6
8
|
ValidationError,
|
|
7
9
|
} from "@typespec/spec-api";
|
|
8
10
|
import { Response } from "express";
|
|
@@ -17,6 +19,7 @@ export async function processRequest(
|
|
|
17
19
|
request: RequestExt,
|
|
18
20
|
response: Response,
|
|
19
21
|
func: MockRequestHandler,
|
|
22
|
+
resolverConfig: ResolverConfig,
|
|
20
23
|
): Promise<void> {
|
|
21
24
|
const mockRequest = new MockRequest(request);
|
|
22
25
|
const mockResponse = await callHandler(mockRequest, response, func);
|
|
@@ -25,18 +28,27 @@ export async function processRequest(
|
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
await coverageTracker.trackEndpointResponse(scenarioName, scenarioUri, mockResponse);
|
|
28
|
-
processResponse(response, mockResponse);
|
|
31
|
+
processResponse(response, mockResponse, resolverConfig);
|
|
29
32
|
}
|
|
30
33
|
|
|
31
|
-
const processResponse = (
|
|
34
|
+
const processResponse = (
|
|
35
|
+
response: Response,
|
|
36
|
+
mockResponse: MockResponse,
|
|
37
|
+
resolverConfig: ResolverConfig,
|
|
38
|
+
) => {
|
|
32
39
|
response.status(mockResponse.status);
|
|
33
40
|
|
|
34
41
|
if (mockResponse.headers) {
|
|
35
|
-
response.set(mockResponse.headers);
|
|
42
|
+
response.set(expandDyns(mockResponse.headers, resolverConfig));
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
if (mockResponse.body) {
|
|
39
|
-
|
|
46
|
+
const raw =
|
|
47
|
+
typeof mockResponse.body.rawContent === "string" ||
|
|
48
|
+
Buffer.isBuffer(mockResponse.body.rawContent)
|
|
49
|
+
? mockResponse.body.rawContent
|
|
50
|
+
: mockResponse.body.rawContent?.serialize(resolverConfig);
|
|
51
|
+
response.contentType(mockResponse.body.contentType).send(raw);
|
|
40
52
|
}
|
|
41
53
|
|
|
42
54
|
response.end();
|
package/src/cli/cli.ts
CHANGED
|
@@ -92,13 +92,14 @@ async function main() {
|
|
|
92
92
|
.command("server", "Server management", (cmd) => {
|
|
93
93
|
cmd
|
|
94
94
|
.command(
|
|
95
|
-
"start <
|
|
95
|
+
"start <scenariosPaths..>",
|
|
96
96
|
"Start the server in the background.",
|
|
97
97
|
(cmd) => {
|
|
98
98
|
return cmd
|
|
99
|
-
.positional("
|
|
100
|
-
description: "Path to the scenarios and mock apis",
|
|
99
|
+
.positional("scenariosPaths", {
|
|
100
|
+
description: "Path(s) to the scenarios and mock apis",
|
|
101
101
|
type: "string",
|
|
102
|
+
array: true,
|
|
102
103
|
demandOption: true,
|
|
103
104
|
})
|
|
104
105
|
.option("port", {
|
|
@@ -115,7 +116,7 @@ async function main() {
|
|
|
115
116
|
},
|
|
116
117
|
async (args) =>
|
|
117
118
|
startInBackground({
|
|
118
|
-
scenariosPath:
|
|
119
|
+
scenariosPath: args.scenariosPaths,
|
|
119
120
|
port: args.port,
|
|
120
121
|
coverageFile: args.coverageFile,
|
|
121
122
|
}),
|
|
@@ -166,7 +167,7 @@ async function main() {
|
|
|
166
167
|
},
|
|
167
168
|
)
|
|
168
169
|
.command(
|
|
169
|
-
"
|
|
170
|
+
"knock <scenariosPaths..>",
|
|
170
171
|
"Executes the test cases against the service",
|
|
171
172
|
(cmd) => {
|
|
172
173
|
return cmd
|
|
@@ -180,23 +181,18 @@ async function main() {
|
|
|
180
181
|
description: "Path to the server",
|
|
181
182
|
type: "string",
|
|
182
183
|
})
|
|
183
|
-
.option("
|
|
184
|
-
description: "
|
|
184
|
+
.option("filter", {
|
|
185
|
+
description: "Glob filter of scenario to run",
|
|
185
186
|
type: "string",
|
|
186
187
|
})
|
|
187
|
-
.
|
|
188
|
-
description: "File that has the Scenarios to run",
|
|
189
|
-
type: "string",
|
|
190
|
-
})
|
|
191
|
-
.demandOption("scenariosPaths", "serverBasePath");
|
|
188
|
+
.demandOption("scenariosPaths");
|
|
192
189
|
},
|
|
193
190
|
async (args) => {
|
|
194
191
|
for (const scenariosPath of args.scenariosPaths) {
|
|
195
192
|
logger.info(`Executing server tests for scenarios at ${scenariosPath}`);
|
|
196
193
|
await serverTest(scenariosPath, {
|
|
197
194
|
baseUrl: args.baseUrl,
|
|
198
|
-
|
|
199
|
-
runScenariosFromFile: args.runScenariosFromFile,
|
|
195
|
+
filter: args.filter,
|
|
200
196
|
});
|
|
201
197
|
}
|
|
202
198
|
},
|
|
@@ -287,20 +283,30 @@ async function main() {
|
|
|
287
283
|
array: true,
|
|
288
284
|
demandOption: true,
|
|
289
285
|
})
|
|
286
|
+
.option("setName", {
|
|
287
|
+
type: "string",
|
|
288
|
+
description: "Set used to generate the manifest.",
|
|
289
|
+
array: true,
|
|
290
|
+
demandOption: true,
|
|
291
|
+
})
|
|
290
292
|
.option("storageAccountName", {
|
|
291
293
|
type: "string",
|
|
292
294
|
description: "Name of the storage account",
|
|
293
295
|
})
|
|
296
|
+
.option("containerName", {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Name of the Container",
|
|
299
|
+
demandOption: true,
|
|
300
|
+
})
|
|
294
301
|
.demandOption("storageAccountName");
|
|
295
302
|
},
|
|
296
303
|
async (args) => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
304
|
+
await uploadScenarioManifest({
|
|
305
|
+
scenariosPaths: args.scenariosPaths,
|
|
306
|
+
storageAccountName: args.storageAccountName,
|
|
307
|
+
setNames: args.setName,
|
|
308
|
+
containerName: args.containerName,
|
|
309
|
+
});
|
|
304
310
|
},
|
|
305
311
|
)
|
|
306
312
|
.command(
|
|
@@ -339,6 +345,11 @@ async function main() {
|
|
|
339
345
|
type: "string",
|
|
340
346
|
description: "Mode of generator to upload.",
|
|
341
347
|
})
|
|
348
|
+
.option("containerName", {
|
|
349
|
+
type: "string",
|
|
350
|
+
description: "Name of the Container",
|
|
351
|
+
demandOption: true,
|
|
352
|
+
})
|
|
342
353
|
.demandOption("generatorMode");
|
|
343
354
|
},
|
|
344
355
|
async (args) => {
|
|
@@ -349,6 +360,7 @@ async function main() {
|
|
|
349
360
|
generatorVersion: args.generatorVersion,
|
|
350
361
|
generatorCommit: args.generatorCommit ?? getCommit(process.cwd()),
|
|
351
362
|
generatorMode: args.generatorMode,
|
|
363
|
+
containerName: args.containerName,
|
|
352
364
|
});
|
|
353
365
|
},
|
|
354
366
|
)
|
package/src/coverage/common.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Fail, KeyedMockResponse, MockResponse, PassByKeyScenario,
|
|
1
|
+
import { Fail, KeyedMockResponse, MockResponse, PassByKeyScenario, ScenarioMockApi } from "@typespec/spec-api";
|
|
2
2
|
import { logger } from "../logger.js";
|
|
3
3
|
import { CoverageReport, ScenariosMetadata, ScenarioStatus } from "@typespec/spec-coverage-sdk";
|
|
4
4
|
import { writeFileSync } from "fs";
|
|
@@ -106,7 +106,7 @@ export class CoverageTracker {
|
|
|
106
106
|
return "pass";
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
function checkByKeys(scenario: PassByKeyScenario
|
|
109
|
+
function checkByKeys(scenario: PassByKeyScenario) {
|
|
110
110
|
for (const endpoint of scenario.apis) {
|
|
111
111
|
const hits = scenarioHits?.get(endpoint.uri);
|
|
112
112
|
if (hits === undefined) {
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { loadScenarios } from "../scenarios-resolver.js";
|
|
2
2
|
import { Diagnostic } from "../utils/diagnostic-reporter.js";
|
|
3
3
|
import { getCommit, getPackageJson } from "../utils/misc-utils.js";
|
|
4
|
-
import { ScenarioLocation, ScenarioManifest
|
|
4
|
+
import { ScenarioLocation, ScenarioManifest } from "@typespec/spec-coverage-sdk";
|
|
5
5
|
import { getSourceLocation, normalizePath } from "@typespec/compiler";
|
|
6
6
|
import { relative } from "path";
|
|
7
7
|
import type { Scenario } from "../lib/decorators.js";
|
|
8
8
|
|
|
9
9
|
export async function computeScenarioManifest(
|
|
10
10
|
scenariosPath: string,
|
|
11
|
+
setName: string
|
|
11
12
|
): Promise<[ScenarioManifest | undefined, readonly Diagnostic[]]> {
|
|
12
13
|
const [scenarios, diagnostics] = await loadScenarios(scenariosPath);
|
|
13
14
|
if (diagnostics.length > 0) {
|
|
@@ -16,7 +17,7 @@ export async function computeScenarioManifest(
|
|
|
16
17
|
|
|
17
18
|
const commit = getCommit(scenariosPath);
|
|
18
19
|
const pkg = await getPackageJson(scenariosPath);
|
|
19
|
-
return [createScenarioManifest(scenariosPath, pkg?.version ?? "?", commit, scenarios), []];
|
|
20
|
+
return [createScenarioManifest(scenariosPath, pkg?.version ?? "?", commit, scenarios, setName), []];
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export function createScenarioManifest(
|
|
@@ -24,6 +25,7 @@ export function createScenarioManifest(
|
|
|
24
25
|
version: string,
|
|
25
26
|
commit: string,
|
|
26
27
|
scenarios: Scenario[],
|
|
28
|
+
setName: string
|
|
27
29
|
): ScenarioManifest {
|
|
28
30
|
const sortedScenarios = [...scenarios].sort((a, b) => a.name.localeCompare(b.name));
|
|
29
31
|
return {
|
|
@@ -38,6 +40,6 @@ export function createScenarioManifest(
|
|
|
38
40
|
};
|
|
39
41
|
return { name, scenarioDoc, location };
|
|
40
42
|
}),
|
|
41
|
-
|
|
43
|
+
setName
|
|
42
44
|
};
|
|
43
45
|
}
|
package/src/lib/decorators.ts
CHANGED
|
@@ -50,10 +50,8 @@ export const $scenarioService: ScenarioServiceDecorator = (context, target, rout
|
|
|
50
50
|
kind: "Model",
|
|
51
51
|
properties,
|
|
52
52
|
decorators: [],
|
|
53
|
-
projections: [],
|
|
54
53
|
name: "Service",
|
|
55
54
|
derivedModels: [],
|
|
56
|
-
projectionsByName: [],
|
|
57
55
|
} as any);
|
|
58
56
|
context.call($server, target, "http://localhost:3000", "TestServer endpoint");
|
|
59
57
|
context.call($route, target, route);
|
package/src/logger.ts
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
|
-
|
|
1
|
+
/* eslint-disable no-console */
|
|
2
|
+
import pc from "picocolors";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
+
const levels = {
|
|
5
|
+
debug: 10,
|
|
6
|
+
info: 20,
|
|
7
|
+
warn: 30,
|
|
8
|
+
error: 30,
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
type Level = keyof typeof levels;
|
|
12
|
+
const levelDisplay: Record<Level, string> = {
|
|
13
|
+
debug: pc.blue("debug"),
|
|
14
|
+
info: pc.green("info"),
|
|
15
|
+
warn: pc.yellow("warn"),
|
|
16
|
+
error: pc.red("error"),
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
interface Logger {
|
|
20
|
+
level: Level;
|
|
21
|
+
debug: (message: string, ...data: unknown[]) => void;
|
|
22
|
+
info: (message: string, ...data: unknown[]) => void;
|
|
23
|
+
warn: (message: string, ...data: unknown[]) => void;
|
|
24
|
+
error: (message: string, ...data: unknown[]) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const logger: Logger = {
|
|
4
28
|
level: "info",
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
29
|
+
debug: log("debug"),
|
|
30
|
+
info: log("info"),
|
|
31
|
+
warn: log("warn"),
|
|
32
|
+
error: log("error"),
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function log(level: Level) {
|
|
36
|
+
return (message: string, ...data: unknown[]) => {
|
|
37
|
+
if (levels[level] >= levels[logger.level]) {
|
|
38
|
+
console.log(levelDisplay[level], message, ...data);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
}
|
package/src/server/server.ts
CHANGED
|
@@ -70,30 +70,44 @@ export class MockApiServer {
|
|
|
70
70
|
this.app.use(bodyParser.text({ type: "text/plain" }));
|
|
71
71
|
this.app.use(
|
|
72
72
|
bodyParser.raw({
|
|
73
|
-
type: ["application/octet-stream", "image/png"],
|
|
73
|
+
type: ["application/octet-stream", "image/png", "application/jsonl"],
|
|
74
74
|
limit: "10mb",
|
|
75
75
|
verify: rawBinaryBodySaver,
|
|
76
76
|
}),
|
|
77
77
|
);
|
|
78
|
-
this.app.use(multer().any());
|
|
78
|
+
this.app.use(multer().any() as any);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
public use(route: string, ...handlers: RequestHandler[]): void {
|
|
82
82
|
this.app.use(route, ...handlers);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
public start():
|
|
85
|
+
public start(): Promise<number> {
|
|
86
86
|
this.app.use(errorHandler);
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
return new Promise((resolve, reject) => {
|
|
89
|
+
const server = this.app.listen(this.config.port, () => {
|
|
90
|
+
const resolvedPort = getPort(server);
|
|
91
|
+
if (!resolvedPort) {
|
|
92
|
+
logger.error("Failed to resolve port");
|
|
93
|
+
reject(new Error("Failed to resolve port"));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
logger.info(`Started server on ${resolvedPort}`);
|
|
97
|
+
resolve(resolvedPort);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
server.on("error", (err) => {
|
|
101
|
+
logger.error("Error starting server", err);
|
|
102
|
+
reject(err);
|
|
103
|
+
});
|
|
90
104
|
});
|
|
91
105
|
}
|
|
92
106
|
}
|
|
93
107
|
|
|
94
108
|
export type ServerRequestHandler = (request: RequestExt, response: Response) => void;
|
|
95
109
|
|
|
96
|
-
const
|
|
110
|
+
const getPort = (server: Server): number | undefined | null => {
|
|
97
111
|
const address = server?.address();
|
|
98
|
-
return typeof address === "string" ?
|
|
112
|
+
return typeof address === "string" ? null : address?.port;
|
|
99
113
|
};
|