@typespec/spector 0.1.0-alpha.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 +118 -0
- 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 +3 -2
- package/dist/src/actions/upload-scenario-manifest.d.ts.map +1 -1
- package/dist/src/actions/upload-scenario-manifest.js +8 -5
- 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 +24 -15
- 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/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 +24 -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 +11 -7
- 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 +24 -15
- package/src/coverage/common.ts +1 -0
- package/src/coverage/coverage-tracker.ts +2 -2
- 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
|
@@ -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
|
},
|
|
@@ -290,19 +286,26 @@ async function main() {
|
|
|
290
286
|
.option("setName", {
|
|
291
287
|
type: "string",
|
|
292
288
|
description: "Set used to generate the manifest.",
|
|
289
|
+
array: true,
|
|
293
290
|
demandOption: true,
|
|
294
291
|
})
|
|
295
292
|
.option("storageAccountName", {
|
|
296
293
|
type: "string",
|
|
297
294
|
description: "Name of the storage account",
|
|
298
295
|
})
|
|
296
|
+
.option("containerName", {
|
|
297
|
+
type: "string",
|
|
298
|
+
description: "Name of the Container",
|
|
299
|
+
demandOption: true,
|
|
300
|
+
})
|
|
299
301
|
.demandOption("storageAccountName");
|
|
300
302
|
},
|
|
301
303
|
async (args) => {
|
|
302
304
|
await uploadScenarioManifest({
|
|
303
305
|
scenariosPaths: args.scenariosPaths,
|
|
304
306
|
storageAccountName: args.storageAccountName,
|
|
305
|
-
|
|
307
|
+
setNames: args.setName,
|
|
308
|
+
containerName: args.containerName,
|
|
306
309
|
});
|
|
307
310
|
},
|
|
308
311
|
)
|
|
@@ -342,6 +345,11 @@ async function main() {
|
|
|
342
345
|
type: "string",
|
|
343
346
|
description: "Mode of generator to upload.",
|
|
344
347
|
})
|
|
348
|
+
.option("containerName", {
|
|
349
|
+
type: "string",
|
|
350
|
+
description: "Name of the Container",
|
|
351
|
+
demandOption: true,
|
|
352
|
+
})
|
|
345
353
|
.demandOption("generatorMode");
|
|
346
354
|
},
|
|
347
355
|
async (args) => {
|
|
@@ -352,6 +360,7 @@ async function main() {
|
|
|
352
360
|
generatorVersion: args.generatorVersion,
|
|
353
361
|
generatorCommit: args.generatorCommit ?? getCommit(process.cwd()),
|
|
354
362
|
generatorMode: args.generatorMode,
|
|
363
|
+
containerName: args.containerName,
|
|
355
364
|
});
|
|
356
365
|
},
|
|
357
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) {
|
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
|
};
|