@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
package/src/app/app.ts CHANGED
@@ -1,4 +1,13 @@
1
- import { MockApiDefinition, MockRequest, RequestExt, ScenarioMockApi } from "@typespec/spec-api";
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
- this.server.start();
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.kind !== "MockApiDefinition") {
63
- this.router.route(endpoint.uri)[endpoint.method]((req: RequestExt, res: Response) => {
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 isObject(value: any): boolean {
99
- return typeof value === "object" && value !== null && !Array.isArray(value);
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 (apiDefinition.request.body) {
106
- if (
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.headers) {
129
- Object.entries(apiDefinition.request.headers).forEach(([key, value]) => {
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
- // Validate query params if present in the request
141
- if (apiDefinition.request.params) {
142
- Object.entries(apiDefinition.request.params).forEach(([key, value]) => {
143
- if (!req.query[key]) {
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
- if (Array.isArray(value)) {
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 = (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
  },
@@ -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
- for (const scenariosPath of args.scenariosPaths) {
298
- logger.info(`Uploading scenario manifest for scenarios at ${scenariosPath}`);
299
- await uploadScenarioManifest({
300
- scenariosPath: resolve(process.cwd(), scenariosPath),
301
- storageAccountName: args.storageAccountName,
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
  )
@@ -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) {
@@ -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, GeneratorMode } from "@typespec/spec-coverage-sdk";
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
- modes: GeneratorMode,
43
+ setName
42
44
  };
43
45
  }
@@ -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
  };