@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.
Files changed (81) hide show
  1. package/CHANGELOG.md +131 -1
  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 +4 -2
  21. package/dist/src/actions/upload-scenario-manifest.d.ts.map +1 -1
  22. package/dist/src/actions/upload-scenario-manifest.js +17 -7
  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 +33 -21
  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/coverage/scenario-manifest.d.ts +2 -2
  42. package/dist/src/coverage/scenario-manifest.d.ts.map +1 -1
  43. package/dist/src/coverage/scenario-manifest.js +4 -5
  44. package/dist/src/coverage/scenario-manifest.js.map +1 -1
  45. package/dist/src/lib/decorators.d.ts.map +1 -1
  46. package/dist/src/lib/decorators.js +0 -2
  47. package/dist/src/lib/decorators.js.map +1 -1
  48. package/dist/src/logger.d.ts +16 -2
  49. package/dist/src/logger.d.ts.map +1 -1
  50. package/dist/src/logger.js +27 -8
  51. package/dist/src/logger.js.map +1 -1
  52. package/dist/src/server/server.d.ts +1 -1
  53. package/dist/src/server/server.d.ts.map +1 -1
  54. package/dist/src/server/server.js +18 -5
  55. package/dist/src/server/server.js.map +1 -1
  56. package/dist/src/utils/body-utils.d.ts.map +1 -1
  57. package/dist/src/utils/request-utils.d.ts.map +1 -1
  58. package/docs/decorators.md +24 -0
  59. package/docs/using-spector.md +99 -0
  60. package/docs/writing-mock-apis.md +116 -0
  61. package/docs/writing-scenario-spec.md +49 -0
  62. package/generated-defs/TypeSpec.Spector.ts-test.ts +7 -2
  63. package/lib/main.tsp +1 -1
  64. package/package.json +23 -28
  65. package/src/actions/helper.ts +79 -95
  66. package/src/actions/serve.ts +3 -4
  67. package/src/actions/server-test.ts +132 -156
  68. package/src/actions/upload-coverage-report.ts +7 -1
  69. package/src/actions/upload-scenario-manifest.ts +22 -9
  70. package/src/actions/validate-mock-apis.ts +1 -1
  71. package/src/app/app.ts +71 -72
  72. package/src/app/request-processor.ts +16 -4
  73. package/src/cli/cli.ts +33 -21
  74. package/src/coverage/common.ts +1 -0
  75. package/src/coverage/coverage-tracker.ts +2 -2
  76. package/src/coverage/scenario-manifest.ts +5 -3
  77. package/src/lib/decorators.ts +0 -2
  78. package/src/logger.ts +39 -8
  79. package/src/scenarios-resolver.ts +1 -1
  80. package/src/server/server.ts +21 -7
  81. package/temp/.tsbuildinfo +1 -1
@@ -1,10 +1,29 @@
1
- import winston from "winston";
2
- export const logger = winston.createLogger({
1
+ /* eslint-disable no-console */
2
+ import pc from "picocolors";
3
+ const levels = {
4
+ debug: 10,
5
+ info: 20,
6
+ warn: 30,
7
+ error: 30,
8
+ };
9
+ const levelDisplay = {
10
+ debug: pc.blue("debug"),
11
+ info: pc.green("info"),
12
+ warn: pc.yellow("warn"),
13
+ error: pc.red("error"),
14
+ };
15
+ export const logger = {
3
16
  level: "info",
4
- transports: [
5
- new winston.transports.Console({
6
- format: winston.format.combine(winston.format.colorize(), winston.format.simple()),
7
- }),
8
- ],
9
- });
17
+ debug: log("debug"),
18
+ info: log("info"),
19
+ warn: log("warn"),
20
+ error: log("error"),
21
+ };
22
+ function log(level) {
23
+ return (message, ...data) => {
24
+ if (levels[level] >= levels[logger.level]) {
25
+ console.log(levelDisplay[level], message, ...data);
26
+ }
27
+ };
28
+ }
10
29
  //# sourceMappingURL=logger.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,MAAM,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IACzC,KAAK,EAAE,MAAM;IACb,UAAU,EAAE;QACV,IAAI,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC;YAC7B,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;SACnF,CAAC;KACH;CACF,CAAC,CAAC"}
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,MAAM,YAAY,CAAC;AAE5B,MAAM,MAAM,GAAG;IACb,KAAK,EAAE,EAAE;IACT,IAAI,EAAE,EAAE;IACR,IAAI,EAAE,EAAE;IACR,KAAK,EAAE,EAAE;CACV,CAAC;AAGF,MAAM,YAAY,GAA0B;IAC1C,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC;IACvB,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC;IACtB,IAAI,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC;IACvB,KAAK,EAAE,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC;CACvB,CAAC;AAUF,MAAM,CAAC,MAAM,MAAM,GAAW;IAC5B,KAAK,EAAE,MAAM;IACb,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC;IACnB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC;IACjB,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC;IACjB,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC;CACpB,CAAC;AAEF,SAAS,GAAG,CAAC,KAAY;IACvB,OAAO,CAAC,OAAe,EAAE,GAAG,IAAe,EAAE,EAAE;QAC7C,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;YAC1C,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;QACrD,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
@@ -8,7 +8,7 @@ export declare class MockApiServer {
8
8
  private app;
9
9
  constructor(config: MockApiServerConfig);
10
10
  use(route: string, ...handlers: RequestHandler[]): void;
11
- start(): void;
11
+ start(): Promise<number>;
12
12
  }
13
13
  export type ServerRequestHandler = (request: RequestExt, response: Response) => void;
14
14
  //# sourceMappingURL=server.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAgB,EAAuB,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOjF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAyCD,qBAAa,aAAa;IAGZ,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,GAAG,CAAsB;gBAEb,MAAM,EAAE,mBAAmB;IAyBxC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI;IAIvD,KAAK,IAAI,IAAI;CAOrB;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC"}
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/server/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,OAAgB,EAAuB,cAAc,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAOjF,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;CACd;AAyCD,qBAAa,aAAa;IAGZ,OAAO,CAAC,MAAM;IAF1B,OAAO,CAAC,GAAG,CAAsB;gBAEb,MAAM,EAAE,mBAAmB;IAyBxC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,QAAQ,EAAE,cAAc,EAAE,GAAG,IAAI;IAIvD,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;CAqBhC;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,KAAK,IAAI,CAAC"}
@@ -45,7 +45,7 @@ export class MockApiServer {
45
45
  this.app.use(bodyParser.text({ type: "*/pdf", verify: rawBodySaver }));
46
46
  this.app.use(bodyParser.text({ type: "text/plain" }));
47
47
  this.app.use(bodyParser.raw({
48
- type: ["application/octet-stream", "image/png"],
48
+ type: ["application/octet-stream", "image/png", "application/jsonl"],
49
49
  limit: "10mb",
50
50
  verify: rawBinaryBodySaver,
51
51
  }));
@@ -56,13 +56,26 @@ export class MockApiServer {
56
56
  }
57
57
  start() {
58
58
  this.app.use(errorHandler);
59
- const server = this.app.listen(this.config.port, () => {
60
- logger.info(`Started server on ${getAddress(server)}`);
59
+ return new Promise((resolve, reject) => {
60
+ const server = this.app.listen(this.config.port, () => {
61
+ const resolvedPort = getPort(server);
62
+ if (!resolvedPort) {
63
+ logger.error("Failed to resolve port");
64
+ reject(new Error("Failed to resolve port"));
65
+ return;
66
+ }
67
+ logger.info(`Started server on ${resolvedPort}`);
68
+ resolve(resolvedPort);
69
+ });
70
+ server.on("error", (err) => {
71
+ logger.error("Error starting server", err);
72
+ reject(err);
73
+ });
61
74
  });
62
75
  }
63
76
  }
64
- const getAddress = (server) => {
77
+ const getPort = (server) => {
65
78
  const address = server?.address();
66
- return typeof address === "string" ? "pipe " + address : "port " + address?.port;
79
+ return typeof address === "string" ? null : address?.port;
67
80
  };
68
81
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/server/server.ts"],"names":[],"mappings":"AACA,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,OAA0D,MAAM,SAAS,CAAC;AAEjF,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMhD,MAAM,YAAY,GAAwB,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;IAClE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAE3B,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM;QAC5B,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE;QACd,CAAC,CAAC,GAAG,YAAY,KAAK;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;YAC5D,CAAC,CAAC,GAAG,CAAC;IAEV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;IAC9B,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC;AAC9D,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CACnB,GAAe,EACf,GAAmB,EACnB,GAAW,EACX,QAAwB,EACxB,EAAE;IACF,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CACzB,GAAe,EACf,GAAmB,EACnB,GAAW,EACX,QAAwB,EACxB,EAAE;IACF,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC;IACpB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG;IACnB,KAAK,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;CACjD,CAAC;AAEF,MAAM,OAAO,aAAa;IAGJ;IAFZ,GAAG,CAAsB;IAEjC,YAAoB,MAA2B;QAA3B,WAAM,GAAN,MAAM,CAAqB;QAC7C,IAAI,CAAC,GAAG,GAAG,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,GAAG,CACV,UAAU,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,KAAK;SACd,CAAC,CACH,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9E,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG,CAAC,GAAG,CACV,UAAU,CAAC,GAAG,CAAC;YACb,IAAI,EAAE,CAAC,0BAA0B,EAAE,WAAW,CAAC;YAC/C,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,kBAAkB;SAC3B,CAAC,CACH,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC;IAC/B,CAAC;IAEM,GAAG,CAAC,KAAa,EAAE,GAAG,QAA0B;QACrD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAC;IACnC,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAE3B,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YACpD,MAAM,CAAC,IAAI,CAAC,qBAAqB,UAAU,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAID,MAAM,UAAU,GAAG,CAAC,MAAc,EAAU,EAAE;IAC5C,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC;IAClC,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,OAAO,EAAE,IAAI,CAAC;AACnF,CAAC,CAAC"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../../src/server/server.ts"],"names":[],"mappings":"AACA,OAAO,UAAU,MAAM,aAAa,CAAC;AACrC,OAAO,OAA0D,MAAM,SAAS,CAAC;AAEjF,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,MAAM,MAAM,QAAQ,CAAC;AAC5B,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAMhD,MAAM,YAAY,GAAwB,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;IAClE,MAAM,CAAC,KAAK,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC;IAE3B,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM;QAC5B,CAAC,CAAC,GAAG,CAAC,MAAM,EAAE;QACd,CAAC,CAAC,GAAG,YAAY,KAAK;YACpB,CAAC,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;YAC5D,CAAC,CAAC,GAAG,CAAC;IAEV,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,CAAC;IAC9B,GAAG,CAAC,WAAW,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,GAAG,EAAE,CAAC;AAC9D,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG,CACnB,GAAe,EACf,GAAmB,EACnB,GAAW,EACX,QAAwB,EACxB,EAAE;IACF,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,QAAQ,IAAI,MAAM,CAAC,CAAC,CAAC;IAC9D,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CACzB,GAAe,EACf,GAAmB,EACnB,GAAW,EACX,QAAwB,EACxB,EAAE;IACF,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACtB,GAAG,CAAC,OAAO,GAAG,GAAG,CAAC;IACpB,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,YAAY,GAAG;IACnB,KAAK,EAAE,CAAC,OAAe,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC;CACjD,CAAC;AAEF,MAAM,OAAO,aAAa;IAGJ;IAFZ,GAAG,CAAsB;IAEjC,YAAoB,MAA2B;QAA3B,WAAM,GAAN,MAAM,CAAqB;QAC7C,IAAI,CAAC,GAAG,GAAG,OAAO,EAAE,CAAC;QACrB,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,GAAG,CACV,UAAU,CAAC,IAAI,CAAC;YACd,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,KAAK;SACd,CAAC,CACH,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;QAC9E,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACvE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC;QACtD,IAAI,CAAC,GAAG,CAAC,GAAG,CACV,UAAU,CAAC,GAAG,CAAC;YACb,IAAI,EAAE,CAAC,0BAA0B,EAAE,WAAW,EAAE,mBAAmB,CAAC;YACpE,KAAK,EAAE,MAAM;YACb,MAAM,EAAE,kBAAkB;SAC3B,CAAC,CACH,CAAC;QACF,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,GAAG,EAAS,CAAC,CAAC;IACtC,CAAC;IAEM,GAAG,CAAC,KAAa,EAAE,GAAG,QAA0B;QACrD,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,GAAG,QAAQ,CAAC,CAAC;IACnC,CAAC;IAEM,KAAK;QACV,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAE3B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;gBACpD,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;gBACrC,IAAI,CAAC,YAAY,EAAE,CAAC;oBAClB,MAAM,CAAC,KAAK,CAAC,wBAAwB,CAAC,CAAC;oBACvC,MAAM,CAAC,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC,CAAC;oBAC5C,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,IAAI,CAAC,qBAAqB,YAAY,EAAE,CAAC,CAAC;gBACjD,OAAO,CAAC,YAAY,CAAC,CAAC;YACxB,CAAC,CAAC,CAAC;YAEH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;gBACzB,MAAM,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;gBAC3C,MAAM,CAAC,GAAG,CAAC,CAAC;YACd,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;CACF;AAID,MAAM,OAAO,GAAG,CAAC,MAAc,EAA6B,EAAE;IAC5D,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,EAAE,CAAC;IAClC,OAAO,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,EAAE,IAAI,CAAC;AAC5D,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"body-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/body-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,eAAgB,MAAM,KAAG,MACH,CAAC"}
1
+ {"version":3,"file":"body-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/body-utils.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GAAI,YAAY,MAAM,KAAG,MACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"request-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/request-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,eAAO,MAAM,iBAAiB,YAAa,OAAO,KAAG,MACL,CAAC"}
1
+ {"version":3,"file":"request-utils.d.ts","sourceRoot":"","sources":["../../../src/utils/request-utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAElC,eAAO,MAAM,iBAAiB,GAAI,SAAS,OAAO,KAAG,MACL,CAAC"}
@@ -0,0 +1,24 @@
1
+ ## Spector decorators
2
+
3
+ ### `@scenarioService`
4
+
5
+ Decorator setting up the boilerplate for specs service namespace. Will automatically set:
6
+
7
+ - `@service{title: '<namespace>', value: '1.0.0'}` using the namespace as a value
8
+ - `@server` to `localhost:3000`
9
+ - `@route` using the parameter passed.
10
+
11
+ Usage:
12
+
13
+ ```tsp
14
+ @scenarioSpec("/my-spec")
15
+ namespace MySpec;
16
+ ```
17
+
18
+ ### `@scenario`
19
+
20
+ Mark an operation, interface or namespace as a scenario. Optionally can provide the name of the scenario.
21
+
22
+ ### `@scenarioDoc`
23
+
24
+ Specify how to implement this scenario. Value is markdown. Differ from @doc which describe the scenario to the end user.
@@ -0,0 +1,99 @@
1
+ # Using spector
2
+
3
+ Install `spector` CLI globally or as a local dependency:
4
+
5
+ - **Globally**: run `npm install -g @typespec/spector`. You can then use `tsp-spector` as a CLI tool.
6
+ - **Locally**: run `npm install @typespec/spector` which should add the dependency to package.json. You can then use `tsp-spector` with `npx tsp-spector`.
7
+
8
+ **NOTE** With local install you'll need to prefix `tsp-spector` with `npx`(unless commands are written directly as npm scripts)
9
+
10
+ ## Test against scenarios
11
+
12
+ ### Spector Config
13
+
14
+ Spector supports a config file where you can configure scenarios that are not supported by your generator.
15
+
16
+ Create a `spector-config.yaml` file.
17
+
18
+ ```yaml
19
+ # List of unsupported scenarios
20
+ unsupportedScenarios:
21
+ - Foo_Bar
22
+ ```
23
+
24
+ ### Run mock api server
25
+
26
+ ```bash
27
+ # Minimal
28
+ tsp-spector serve ./path/to/scenarios
29
+
30
+ # Change the port
31
+ tsp-spector serve ./path/to/scenarios --port 1234
32
+
33
+ # Specify where the coverage file should go
34
+ tsp-spector serve ./path/to/scenarios --coverageFile ./path/to/spector-coverage.json
35
+ ```
36
+
37
+ Alternative to start in background
38
+
39
+ ```bash
40
+ tsp-spector server start ./path/to/scenarios # Takes the same arguments as serve
41
+ ```
42
+
43
+ ### Stop running server
44
+
45
+ ```bash
46
+ tsp-spector server stop # Stop at the default port
47
+ tsp-spector server stop --port 1234 # If started the server at another port
48
+ ```
49
+
50
+ ### Validate and merge coverage
51
+
52
+ ```bash
53
+ # Minimal
54
+ tsp-spector check-coverage ./path/to/scenarios
55
+
56
+ # Path to tsp-spector config file for generator
57
+ tsp-spector check-coverage ./path/to/scenarios --configFile ./spector-config.yaml
58
+
59
+ # In case where there was multiple serve instance each creating their own coverage file
60
+ tsp-spector check-coverage ./path/to/scenarios --coverageFiles ./path/to/*-coverage.json --coverageFiles ./other/to/*-coverage.json
61
+
62
+ # Specify where the merged coverage file should go
63
+ tsp-spector check-coverage ./path/to/scenarios --mergedCoverageFile ./path/to/spector-final-coverage.json
64
+ ```
65
+
66
+ ### Upload coverage
67
+
68
+ Upload the coverage. Upload from the `main` branch. DO NOT upload on PR this WILL override the latest index.
69
+
70
+ ```bash
71
+ # Minimal
72
+ tsp-spector upload-coverage --generatorName typescript --version=0.1.0
73
+
74
+ # Specify Coverage file
75
+ tsp-spector upload-coverage --generatorName typescript --version=0.1.0 --coverageFile ./path/to/spector-final-coverage.json
76
+ ```
77
+
78
+ Options:
79
+
80
+ - `--storageAccountName`: Name of the storage account to publish coverage. Use `typespec` for Spector/Azure Spector dashboard.
81
+ - `--generatorName`: Name of the generator. Must be one of `"@typespec/http-client-python", "@typespec/http-client-csharp", "@azure-tools/typespec-ts-rlc", "@azure-tools/typespec-ts-modular", "@typespec/http-client-js", "@typespec/http-client-java"`.
82
+ - `--generatorVersion`: Version of the generator
83
+ - `--coverageFile`: Path to the coverage file
84
+
85
+ #### Upload in azure devops
86
+
87
+ **This is applicable in the azure-sdk/internal ado project only.**
88
+
89
+ Add the following step
90
+
91
+ ```yaml
92
+ - task: AzureCLI@2
93
+ displayName: Upload scenario coverage
94
+ inputs:
95
+ azureSubscription: "Typespec Storage"
96
+ scriptType: "bash"
97
+ scriptLocation: "inlineScript"
98
+ inlineScript: `tsp-spector upload-coverage --storageAccountName typespec --containerName coverages --generatorMode standard ... FILL options fitting your generator here as described above...`
99
+ ```
@@ -0,0 +1,116 @@
1
+ # Writing mock apis
2
+
3
+ 1. Create a `mockapi.ts` file next to the `main.tsp` of the scenario
4
+ 2. Create and export a variable called `Scenarios` with a `Record<string, ScenarioMockApi>` type
5
+ 3. For each of the scenario assign a new property to the `Scenarios` variable. The value use one of the following:
6
+ - `passOnSuccess`: This will take one or multiple routes and will only pass teh scenario if all the routes gets called with a 2xx success code.
7
+
8
+ ## Example
9
+
10
+ ```ts
11
+ import { passOnSuccess, mockapi } from "@typespec/spec-api";
12
+ import { ScenarioMockApi } from "@typespec/spec-api";
13
+
14
+ export const Scenarios: Record<string, ScenarioMockApi> = {};
15
+
16
+ Scenarios.Hello_world = passOnSuccess(
17
+ mockapi.get("/hello/world", () => {
18
+ return {
19
+ status: 200,
20
+ body: {
21
+ contentType: "application/json",
22
+ rawContent: `"Hello World!"`,
23
+ },
24
+ };
25
+ }),
26
+ );
27
+ ```
28
+
29
+ ## How to build response
30
+
31
+ Return the response object. [See type](../../spec-api/src/types.ts)
32
+
33
+ ```ts
34
+ // Minimum requirement is the status code.
35
+ return {
36
+ status: 200,
37
+ };
38
+ ```
39
+
40
+ ### Return a body
41
+
42
+ ```ts
43
+ // Return json
44
+ return {
45
+ status: 200,
46
+ body: json({ foo: 123 }),
47
+ };
48
+
49
+ // Return raw content
50
+ return {
51
+ status: 200,
52
+ body: {
53
+ contentType: "application/text",
54
+ rawContent: "foobar",
55
+ },
56
+ };
57
+ ```
58
+
59
+ ### Return headers
60
+
61
+ ```ts
62
+ // Return json
63
+ return {
64
+ status: 200,
65
+ headers: {
66
+ MyHeader: "value-1"
67
+ MyHeaderOther: req.headers.MyRequestHeader
68
+ }
69
+ };
70
+
71
+ ```
72
+
73
+ ## How to validate the request:
74
+
75
+ All built-in validation tools can be accessed using `req.expect.`
76
+
77
+ ### Validate the body
78
+
79
+ - With `req.expect.bodyEquals`
80
+
81
+ This will do a deep equals of the body to make sure it match.
82
+
83
+ ```ts
84
+ app.post("/example", "Example", (req) => {
85
+ req.bodyEquals({ foo: "123", bar: "456" });
86
+ });
87
+ ```
88
+
89
+ - With `req.expect.rawBodyEquals`
90
+
91
+ This will compare the raw body sent.
92
+
93
+ ```ts
94
+ app.post("/example", "Example", (req) => {
95
+ req.rawBodyEquals('"foo"');
96
+ });
97
+ ```
98
+
99
+ ### Custom validation
100
+
101
+ You can do any kind of validation accessing the `req: MockRequest` object and deciding to return a different response in some cases.
102
+ You can also always `throw` a `ValidationError`
103
+
104
+ Example:
105
+
106
+ ```ts
107
+ app.post("/example", "Example", (req) => {
108
+ if (req.headers.MyCustomHeader.startsWith("x-foo")) {
109
+ throw new ValidationError(
110
+ "MyCustomHeader shouldn't start with x-foo",
111
+ null,
112
+ req.headers.MyCustomHeader,
113
+ );
114
+ }
115
+ });
116
+ ```
@@ -0,0 +1,49 @@
1
+ ## Defining scenario
2
+
3
+ The goal of the testserver is to define scenarios that needs to be supported in client generators. This means to have a meaningful coverage we need scenarios to:
4
+
5
+ - have a specific behavior that is meant to be tested
6
+ - ❌ DO NOT use the same scenario for testing multiple things.
7
+ - have a meaningful name that can be used in a compatibility table to see what a given generator support(e.g. `get_string`)
8
+ - a description of what the scenario is validating(e.g. `"Support passing a simple string as JSON"`)
9
+ - have a good description on what the client is expecting to generate/receive/send(e.g `Validate that this operation returns a JSON string that match "abc"`)
10
+ - ✅ DO describe how to validate that this scenario is working from the client point of view
11
+
12
+ When naming scenario always think about what would it look like in the [compatibility table](#compatibility-table) and would that name be meaningful to someone looking to see what is supported.
13
+
14
+ ```cadl
15
+ import "@typespec/spector";
16
+
17
+ @scenarioService("/strings")
18
+ namespace String;
19
+
20
+ @scenario("get_string")
21
+ @doc("Support passing a simple string as JSON")
22
+ @scenarioDoc("In this scenario the Client should expect a string matching 'abc' to be returned.")
23
+ @get
24
+ @route("/simple")
25
+ op returnString(): string;
26
+ ```
27
+
28
+ Decorators that should be provided in this test library `@typespec/spector`:
29
+
30
+ - `@scenario`: Specify that this operation, interface or namespace is a scenario. Optionally take a scenario name otherwise default to the namespace name + operation/interface name
31
+ - `@scenarioDoc`: Specify how to implement this scenario. Differ from `@doc` which describe the scenario to the end user.
32
+ - `@supportedBy`: Specify if something is supported only by some kind of SDK. Option: `arm`, `dpg`. By default everything.
33
+
34
+ ## Compatibility table
35
+
36
+ With all this information, a detailed compatibility table should be able to be produced by compiling each one of the scenarios and extracting the cases. Providing something like
37
+
38
+ | Scenario | CSharp | Python | Go | Java | TS/JS |
39
+ | ---------------------------- | ------ | ------ | --- | ---- | ----- |
40
+ | Vanilla |
41
+ | `get_string` | ✅ | ✅ | ✅ | ✅ | ✅ |
42
+ | `put_string` | ✅ | ✅ | ✅ | ✅ | ✅ |
43
+ | Azure |
44
+ | `pageable_nextLink` | ✅ | ✅ | ✅ | ✅ | ✅ |
45
+ | `pageable_continuationToken` | ❌ | ✅ | ✅ | ✅ | ✅ |
46
+
47
+ ## Writing the mock api
48
+
49
+ Each scenario should be accompanied by a mock api. See [writing a mock api docs](./writing-mock-apis.md)
@@ -1,5 +1,10 @@
1
- /** An error here would mean that the decorator is not exported or doesn't have the right name. */
1
+ // An error in the imports would mean that the decorator is not exported or
2
+ // doesn't have the right name.
3
+
2
4
  import { $decorators } from "@typespec/spector";
3
5
  import type { TypeSpecSpectorDecorators } from "./TypeSpec.Spector.js";
4
- /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */
6
+
7
+ /**
8
+ * An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ...
9
+ */
5
10
  const _: TypeSpecSpectorDecorators = $decorators["TypeSpec.Spector"];
package/lib/main.tsp CHANGED
@@ -23,7 +23,7 @@ extern dec scenarioService(
23
23
  /**
24
24
  * Mark an operation, interface or namespace as a scenario. All containing operations will be part of the same scenario.
25
25
  */
26
- extern dec scenario(target: Namespace | Interface | Operation, name?: valueof string);
26
+ extern dec `scenario`(target: Namespace | Interface | Operation, name?: valueof string);
27
27
 
28
28
  /**
29
29
  * Specify documentation on how to implement this scenario.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@typespec/spector",
3
- "version": "0.1.0-alpha.2-dev.2",
3
+ "version": "0.1.0-alpha.20-dev.0",
4
4
  "description": "Typespec Core Tool to validate, run mock api, collect coverage.",
5
5
  "exports": {
6
6
  ".": {
@@ -26,51 +26,46 @@
26
26
  },
27
27
  "homepage": "https://github.com/microsoft/typespec#readme",
28
28
  "dependencies": {
29
- "@azure/identity": "~4.4.1",
29
+ "@azure/identity": "~4.12.0",
30
30
  "@types/js-yaml": "^4.0.5",
31
- "@typespec/compiler": "~0.61.2 || >=0.62.0-dev <0.62.0",
32
- "@typespec/versioning": "~0.61.0 || >=0.62.0-dev <0.62.0",
33
- "@typespec/http": "~0.61.0 || >=0.62.0-dev <0.62.0",
34
- "@typespec/rest": "~0.61.0 || >=0.62.0-dev <0.62.0",
35
- "@typespec/spec-api": "~0.1.0-alpha.0 || >=0.1.0-alpha.1-dev <0.1.0-alpha.1",
36
- "@typespec/spec-coverage-sdk": "~0.1.0-alpha.0 || >=0.1.0-alpha.1-dev <0.1.0-alpha.1",
31
+ "@typespec/compiler": "^1.5.0",
32
+ "@typespec/http": "^1.5.0",
33
+ "@typespec/rest": "^0.75.0 || >=0.76.0-dev <0.76.0",
34
+ "@typespec/spec-api": "^0.1.0-alpha.9 || >=0.1.0-alpha.10-dev <0.1.0-alpha.10",
35
+ "@typespec/spec-coverage-sdk": "^0.1.0-alpha.11 || >=0.1.0-alpha.12-dev <0.1.0-alpha.12",
36
+ "@typespec/versioning": "^0.75.0 || >=0.76.0-dev <0.76.0",
37
37
  "ajv": "~8.17.1",
38
- "axios": "^1.7.5",
39
- "body-parser": "^1.20.3",
38
+ "body-parser": "^2.2.0",
40
39
  "deep-equal": "^2.2.0",
41
- "express": "^4.21.1",
42
- "express-promise-router": "^4.1.1",
43
- "form-data": "^4.0.1",
44
- "globby": "~14.0.2",
45
- "jackspeak": "4.0.2",
40
+ "express": "^5.1.0",
41
+ "globby": "~14.1.0",
46
42
  "js-yaml": "^4.1.0",
43
+ "micromatch": "^4.0.8",
47
44
  "morgan": "^1.10.0",
48
- "multer": "^1.4.5-lts.1",
49
- "node-fetch": "^3.3.1",
50
- "picocolors": "~1.1.0",
45
+ "multer": "^2.0.1",
46
+ "picocolors": "~1.1.1",
51
47
  "source-map-support": "~0.5.21",
52
- "winston": "^3.15.0",
53
48
  "xml2js": "^0.6.2",
54
- "yargs": "~17.7.2"
49
+ "yargs": "~18.0.0"
55
50
  },
56
51
  "devDependencies": {
57
52
  "@types/body-parser": "^1.19.2",
58
53
  "@types/deep-equal": "^1.0.1",
59
- "@types/express": "^5.0.0",
60
- "@types/morgan": "^1.9.4",
61
- "@types/multer": "^1.4.10",
62
- "@types/node": "~22.7.5",
63
- "@types/node-fetch": "^2.6.3",
54
+ "@types/express": "^5.0.1",
55
+ "@types/micromatch": "^4.0.9",
56
+ "@types/morgan": "^1.9.9",
57
+ "@types/multer": "^2.0.0",
58
+ "@types/node": "~24.3.0",
64
59
  "@types/xml2js": "^0.4.11",
65
60
  "@types/yargs": "~17.0.33",
61
+ "@typespec/tspd": "^0.73.0 || >=0.74.0-dev <0.74.0",
66
62
  "rimraf": "~6.0.1",
67
- "typescript": "~5.6.3",
68
- "@typespec/tspd": "~0.46.0"
63
+ "typescript": "~5.9.2"
69
64
  },
70
65
  "peerDependencies": {},
71
66
  "scripts": {
72
67
  "watch": "tsc -p ./tsconfig.build.json --watch",
73
- "build": "npm run gen-extern-signature && tsc -p tsconfig.build.json",
68
+ "build": "pnpm gen-extern-signature && tsc -p tsconfig.build.json",
74
69
  "clean": "rimraf dist/ temp/",
75
70
  "gen-extern-signature": "tspd --enable-experimental gen-extern-signature .",
76
71
  "test": "vitest run",