@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.
Files changed (76) hide show
  1. package/CHANGELOG.md +118 -0
  2. package/README.md +37 -0
  3. package/dist/generated-defs/TypeSpec.Spector.ts-test.js +5 -2
  4. package/dist/generated-defs/TypeSpec.Spector.ts-test.js.map +1 -1
  5. package/dist/src/actions/helper.d.ts +8 -15
  6. package/dist/src/actions/helper.d.ts.map +1 -1
  7. package/dist/src/actions/helper.js +61 -69
  8. package/dist/src/actions/helper.js.map +1 -1
  9. package/dist/src/actions/serve.d.ts.map +1 -1
  10. package/dist/src/actions/serve.js +3 -4
  11. package/dist/src/actions/serve.js.map +1 -1
  12. package/dist/src/actions/server-test.d.ts +3 -5
  13. package/dist/src/actions/server-test.d.ts.map +1 -1
  14. package/dist/src/actions/server-test.js +108 -132
  15. package/dist/src/actions/server-test.js.map +1 -1
  16. package/dist/src/actions/upload-coverage-report.d.ts +2 -1
  17. package/dist/src/actions/upload-coverage-report.d.ts.map +1 -1
  18. package/dist/src/actions/upload-coverage-report.js +6 -2
  19. package/dist/src/actions/upload-coverage-report.js.map +1 -1
  20. package/dist/src/actions/upload-scenario-manifest.d.ts +3 -2
  21. package/dist/src/actions/upload-scenario-manifest.d.ts.map +1 -1
  22. package/dist/src/actions/upload-scenario-manifest.js +8 -5
  23. package/dist/src/actions/upload-scenario-manifest.js.map +1 -1
  24. package/dist/src/actions/validate-mock-apis.js +1 -1
  25. package/dist/src/actions/validate-mock-apis.js.map +1 -1
  26. package/dist/src/app/app.d.ts +1 -0
  27. package/dist/src/app/app.d.ts.map +1 -1
  28. package/dist/src/app/app.js +48 -54
  29. package/dist/src/app/app.js.map +1 -1
  30. package/dist/src/app/request-processor.d.ts +2 -2
  31. package/dist/src/app/request-processor.d.ts.map +1 -1
  32. package/dist/src/app/request-processor.js +10 -6
  33. package/dist/src/app/request-processor.js.map +1 -1
  34. package/dist/src/cli/cli.js +24 -15
  35. package/dist/src/cli/cli.js.map +1 -1
  36. package/dist/src/coverage/common.d.ts.map +1 -1
  37. package/dist/src/coverage/common.js +1 -0
  38. package/dist/src/coverage/common.js.map +1 -1
  39. package/dist/src/coverage/coverage-tracker.d.ts.map +1 -1
  40. package/dist/src/coverage/coverage-tracker.js.map +1 -1
  41. package/dist/src/lib/decorators.d.ts.map +1 -1
  42. package/dist/src/lib/decorators.js +0 -2
  43. package/dist/src/lib/decorators.js.map +1 -1
  44. package/dist/src/logger.d.ts +16 -2
  45. package/dist/src/logger.d.ts.map +1 -1
  46. package/dist/src/logger.js +27 -8
  47. package/dist/src/logger.js.map +1 -1
  48. package/dist/src/server/server.d.ts +1 -1
  49. package/dist/src/server/server.d.ts.map +1 -1
  50. package/dist/src/server/server.js +18 -5
  51. package/dist/src/server/server.js.map +1 -1
  52. package/dist/src/utils/body-utils.d.ts.map +1 -1
  53. package/dist/src/utils/request-utils.d.ts.map +1 -1
  54. package/docs/decorators.md +24 -0
  55. package/docs/using-spector.md +99 -0
  56. package/docs/writing-mock-apis.md +116 -0
  57. package/docs/writing-scenario-spec.md +49 -0
  58. package/generated-defs/TypeSpec.Spector.ts-test.ts +7 -2
  59. package/lib/main.tsp +1 -1
  60. package/package.json +24 -28
  61. package/src/actions/helper.ts +79 -95
  62. package/src/actions/serve.ts +3 -4
  63. package/src/actions/server-test.ts +132 -156
  64. package/src/actions/upload-coverage-report.ts +7 -1
  65. package/src/actions/upload-scenario-manifest.ts +11 -7
  66. package/src/actions/validate-mock-apis.ts +1 -1
  67. package/src/app/app.ts +71 -72
  68. package/src/app/request-processor.ts +16 -4
  69. package/src/cli/cli.ts +24 -15
  70. package/src/coverage/common.ts +1 -0
  71. package/src/coverage/coverage-tracker.ts +2 -2
  72. package/src/lib/decorators.ts +0 -2
  73. package/src/logger.ts +39 -8
  74. package/src/scenarios-resolver.ts +1 -1
  75. package/src/server/server.ts +21 -7
  76. 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 = (response: Response, mockResponse: MockResponse) => {
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
- response.contentType(mockResponse.body.contentType).send(mockResponse.body.rawContent);
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 <scenariosPath>",
95
+ "start <scenariosPaths..>",
96
96
  "Start the server in the background.",
97
97
  (cmd) => {
98
98
  return cmd
99
- .positional("scenariosPath", {
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: resolve(process.cwd(), args.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
- "server-test <scenariosPaths..>",
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("runSingleScenario", {
184
- description: "Single Scenario Case to run",
184
+ .option("filter", {
185
+ description: "Glob filter of scenario to run",
185
186
  type: "string",
186
187
  })
187
- .option("runScenariosFromFile", {
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
- runSingleScenario: args.runSingleScenario,
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
- setName: args.setName,
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
  )
@@ -6,5 +6,6 @@ export async function getScenarioMetadata(scenariosPath: string): Promise<Scenar
6
6
  return {
7
7
  commit: getCommit(scenariosPath),
8
8
  version: pkg?.version ?? "?",
9
+ packageName: pkg?.name ?? "@typespec/http-specs",
9
10
  };
10
11
  }
@@ -1,4 +1,4 @@
1
- import { Fail, KeyedMockResponse, MockResponse, PassByKeyScenario, PassByServiceKeyScenario, ScenarioMockApi } from "@typespec/spec-api";
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 | PassByServiceKeyScenario) {
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) {
@@ -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
- import winston from "winston";
1
+ /* eslint-disable no-console */
2
+ import pc from "picocolors";
2
3
 
3
- export const logger = winston.createLogger({
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
- transports: [
6
- new winston.transports.Console({
7
- format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
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
+ }
@@ -1,4 +1,4 @@
1
- import { Operation } from "@typespec/compiler";
1
+ import type { Operation } from "@typespec/compiler";
2
2
  import { isSharedRoute } from "@typespec/http";
3
3
  import { ScenarioMockApi } from "@typespec/spec-api";
4
4
  import { dirname, join, relative, resolve } from "path";
@@ -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(): void {
85
+ public start(): Promise<number> {
86
86
  this.app.use(errorHandler);
87
87
 
88
- const server = this.app.listen(this.config.port, () => {
89
- logger.info(`Started server on ${getAddress(server)}`);
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 getAddress = (server: Server): string => {
110
+ const getPort = (server: Server): number | undefined | null => {
97
111
  const address = server?.address();
98
- return typeof address === "string" ? "pipe " + address : "port " + address?.port;
112
+ return typeof address === "string" ? null : address?.port;
99
113
  };