counterfact 0.37.2 → 0.38.1

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.
@@ -46,12 +46,17 @@ export class Registry {
46
46
  status: 404,
47
47
  });
48
48
  }
49
- return async ({ ...requestData }) => await execute({
50
- ...requestData,
51
- headers: castParameters(requestData.headers, parameterTypes.header),
52
- matchedPath: handler.matchedPath,
53
- path: castParameters(handler.path, parameterTypes.path),
54
- query: castParameters(requestData.query, parameterTypes.query),
55
- });
49
+ return async ({ ...requestData }) => {
50
+ const operationArgument = {
51
+ ...requestData,
52
+ headers: castParameters(requestData.headers, parameterTypes.header),
53
+ matchedPath: handler.matchedPath,
54
+ path: castParameters(handler.path, parameterTypes.path),
55
+ query: castParameters(requestData.query, parameterTypes.query),
56
+ };
57
+ // eslint-disable-next-line id-length
58
+ operationArgument.x = operationArgument;
59
+ return await execute(operationArgument);
60
+ };
56
61
  }
57
62
  }
@@ -24,6 +24,7 @@ type OmitValueWhenNever<Base> = Pick<
24
24
  interface OpenApiResponse {
25
25
  content: { [key: MediaType]: OpenApiContent };
26
26
  headers: { [key: string]: OpenApiHeader };
27
+ requiredHeaders: string
27
28
  }
28
29
 
29
30
  interface OpenApiResponses {
@@ -41,39 +42,40 @@ type MaybeShortcut<
41
42
  Response["content"],
42
43
  ContentType,
43
44
  (body: Response["content"][ContentType]["schema"]) => GenericResponseBuilder<{
44
- content: Omit<Response["content"], ContentType>;
45
+ content: NeverIfEmpty<Omit<Response["content"], ContentType>>;
45
46
  headers: Response["headers"];
47
+ requiredHeaders: Response["requiredHeaders"];
46
48
  }>,
47
49
  never
48
50
  >;
49
51
 
52
+ type NeverIfEmpty<Record> = {} extends Record ? never : Record;
53
+
50
54
  type MatchFunction<Response extends OpenApiResponse> = <
51
55
  ContentType extends MediaType & keyof Response["content"],
52
56
  >(
53
57
  contentType: ContentType,
54
- body: Response["content"][ContentType]["schema"],
58
+ body: Response["content"][ContentType]["schema"]
55
59
  ) => GenericResponseBuilder<{
56
- content: Omit<Response["content"], ContentType>;
60
+ content: NeverIfEmpty<Omit<Response["content"], ContentType>>;
57
61
  headers: Response["headers"];
62
+ requiredHeaders: Response["requiredHeaders"];
58
63
  }>;
59
64
 
60
65
  type HeaderFunction<Response extends OpenApiResponse> = <
61
66
  Header extends string & keyof Response["headers"],
62
67
  >(
63
68
  header: Header,
64
- value: Response["headers"][Header]["schema"],
69
+ value: Response["headers"][Header]["schema"]
65
70
  ) => GenericResponseBuilder<{
66
- content: Response["content"];
67
- headers: Omit<Response["headers"], Header>;
71
+ content: NeverIfEmpty<Response["content"]>;
72
+ headers: NeverIfEmpty<Omit<Response["headers"], Header>>;
73
+ requiredHeaders: Exclude<Response["requiredHeaders"], Header>;
68
74
  }>;
69
75
 
70
76
  type RandomFunction<Response extends OpenApiResponse> = <
71
77
  Header extends string & keyof Response["headers"],
72
- >() => GenericResponseBuilder<{
73
- content: {};
74
- headers: Response["headers"];
75
- }>;
76
-
78
+ >() => "COUNTERFACT_RESPONSE";
77
79
 
78
80
  interface ResponseBuilder {
79
81
  [status: number | `${number} ${string}`]: ResponseBuilder;
@@ -90,23 +92,27 @@ interface ResponseBuilder {
90
92
  xml: (body: unknown) => ResponseBuilder;
91
93
  }
92
94
 
95
+ type GenericResponseBuilderInner<
96
+ Response extends OpenApiResponse = OpenApiResponse,
97
+ > = OmitValueWhenNever<{
98
+ header: [keyof Response["headers"]] extends [never]
99
+ ? never
100
+ : HeaderFunction<Response>;
101
+ html: MaybeShortcut<"text/html", Response>;
102
+ json: MaybeShortcut<"application/json", Response>;
103
+ match: [keyof Response["content"]] extends [never]
104
+ ? never
105
+ : MatchFunction<Response>;
106
+ random: [keyof Response["content"]] extends [never]
107
+ ? never
108
+ : RandomFunction<Response>;
109
+ text: MaybeShortcut<"text/plain", Response>;
110
+ xml: MaybeShortcut<"application/xml" | "text/xml", Response>;
111
+ }>;
112
+
93
113
  type GenericResponseBuilder<
94
114
  Response extends OpenApiResponse = OpenApiResponse,
95
- > = [keyof Response["content"]] extends [never]
96
- ? { }
97
- : OmitValueWhenNever<{
98
- header: [keyof Response["headers"]] extends [never]
99
- ? never
100
- : HeaderFunction<Response>;
101
- html: MaybeShortcut<"text/html", Response>;
102
- json: MaybeShortcut<"application/json", Response>;
103
- match: [keyof Response["content"]] extends [never]
104
- ? never
105
- : MatchFunction<Response>;
106
- random: [keyof Response["content"]] extends [never] ? never : RandomFunction<Response>;
107
- text: MaybeShortcut<"text/plain", Response>;
108
- xml: MaybeShortcut<"application/xml" | "text/xml", Response>;
109
- }>;
115
+ > = {} extends OmitValueWhenNever<Response> ? "COUNTERFACT_RESPONSE" : keyof OmitValueWhenNever<Response> extends "headers" ? { header: HeaderFunction<Response>, ALL_REMAINING_HEADERS_ARE_OPTIONAL: "COUNTERFACT_RESPONSE" } : GenericResponseBuilderInner<Response>;
110
116
 
111
117
  type ResponseBuilderFactory<
112
118
  Responses extends OpenApiResponses = OpenApiResponses,
@@ -199,6 +205,26 @@ interface OpenApiOperation {
199
205
  };
200
206
  }
201
207
 
208
+ type WideResponseBuilder = {
209
+ header: (body: unknown) => WideResponseBuilder;
210
+ html: (body: unknown) => WideResponseBuilder;
211
+ json: (body: unknown) => WideResponseBuilder;
212
+ match: (contentType: string, body: unknown) => WideResponseBuilder;
213
+ random: () => WideResponseBuilder;
214
+ text: (body: unknown) => WideResponseBuilder;
215
+ xml: (body: unknown) => WideResponseBuilder;
216
+ }
217
+
218
+ type WideOperationArgument = {
219
+ path: Record<string, string>;
220
+ query: Record<string, string>;
221
+ header: Record<string, string>;
222
+ body: unknown;
223
+ response: Record<number, WideResponseBuilder>;
224
+ proxy: (url: string) => { proxyUrl: string };
225
+ context: unknown
226
+ };
227
+
202
228
  export type {
203
229
  HttpStatusCode,
204
230
  MediaType,
@@ -207,4 +233,8 @@ export type {
207
233
  OpenApiResponse,
208
234
  ResponseBuilder,
209
235
  ResponseBuilderFactory,
236
+ WideOperationArgument,
237
+ OmitValueWhenNever
210
238
  };
239
+
240
+
@@ -53,28 +53,24 @@ export class OperationTypeCoder extends Coder {
53
53
  write(script) {
54
54
  // eslint-disable-next-line no-param-reassign
55
55
  script.comments = READ_ONLY_COMMENTS;
56
+ const xType = script.importSharedType("WideOperationArgument");
57
+ script.importSharedType("OmitValueWhenNever");
56
58
  const contextTypeImportName = script.importExternalType("Context", CONTEXT_FILE_TOKEN);
57
59
  const parameters = this.requirement.get("parameters");
58
- const queryType = parameters === undefined
59
- ? "never"
60
- : new ParametersTypeCoder(parameters, "query").write(script);
61
- const pathType = parameters === undefined
62
- ? "never"
63
- : new ParametersTypeCoder(parameters, "path").write(script);
64
- const headerType = parameters === undefined
65
- ? "never"
66
- : new ParametersTypeCoder(parameters, "header").write(script);
60
+ const queryType = new ParametersTypeCoder(parameters, "query").write(script);
61
+ const pathType = new ParametersTypeCoder(parameters, "path").write(script);
62
+ const headerType = new ParametersTypeCoder(parameters, "header").write(script);
67
63
  const bodyRequirement = this.requirement.get("consumes")
68
64
  ? parameters
69
65
  .find((parameter) => ["body", "formData"].includes(parameter.get("in").data))
70
66
  .get("schema")
71
67
  : this.requirement.select("requestBody/content/application~1json/schema");
72
- const bodyType = bodyRequirement
73
- ? new SchemaTypeCoder(bodyRequirement).write(script)
74
- : "undefined";
68
+ const bodyType = bodyRequirement === undefined
69
+ ? "never"
70
+ : new SchemaTypeCoder(bodyRequirement).write(script);
75
71
  const responseType = new ResponseTypeCoder(this.requirement.get("responses"), this.requirement.get("produces")?.data ??
76
72
  this.requirement.specification?.rootRequirement?.get("produces")?.data).write(script);
77
- const proxyType = "(url: string) => { proxyUrl: string }";
78
- return `({ query, path, header, body, context, proxy }: { query: ${queryType}, path: ${pathType}, header: ${headerType}, body: ${bodyType}, context: ${contextTypeImportName}, response: ${responseType}, proxy: ${proxyType} }) => ${this.responseTypes(script)} | { status: 415, contentType: "text/plain", body: string } | { }`;
73
+ const proxyType = '(url: string) => "COUNTERFACT_RESPONSE"';
74
+ return `($: OmitValueWhenNever<{ query: ${queryType}, path: ${pathType}, header: ${headerType}, body: ${bodyType}, context: ${contextTypeImportName}, response: ${responseType}, x: ${xType}, proxy: ${proxyType} }>) => ${this.responseTypes(script)} | { status: 415, contentType: "text/plain", body: string } | "COUNTERFACT_RESPONSE" | { ALL_REMAINING_HEADERS_ARE_OPTIONAL: "COUNTERFACT_RESPONSE" }`;
79
75
  }
80
76
  }
@@ -10,7 +10,7 @@ export class ParametersTypeCoder extends Coder {
10
10
  return super.names("parameters");
11
11
  }
12
12
  write(script) {
13
- const typeDefinitions = this.requirement.data
13
+ const typeDefinitions = (this.requirement?.data ?? [])
14
14
  .filter((parameter) => parameter.in === this.placement)
15
15
  .map((parameter, index) => {
16
16
  const requirement = this.requirement.get(String(index));
@@ -33,19 +33,13 @@ export class Repository {
33
33
  }
34
34
  }
35
35
  async copyCoreFiles(destination) {
36
- const sourcePath = nodePath
37
- .join(__dirname, "../../dist/server/types.d.ts")
38
- .replaceAll("\\", "/");
36
+ const sourcePath = nodePath.join(__dirname, "../../dist/server/types.d.ts");
37
+ const destinationPath = nodePath.join(destination, "types.d.ts");
39
38
  if (!existsSync(sourcePath)) {
40
39
  return false;
41
40
  }
42
- const destinationPath = nodePath
43
- .join(destination, "types.d.ts")
44
- .replaceAll("\\", "/");
45
41
  await ensureDirectoryExists(destination);
46
- return fs.copyFile(nodePath
47
- .join(__dirname, "../../dist/server/types.d.ts")
48
- .replaceAll("\\", "/"), destinationPath);
42
+ return fs.copyFile(sourcePath, destinationPath);
49
43
  }
50
44
  async writeFiles(destination) {
51
45
  debug("waiting for %i or more scripts to finish before writing files", this.scripts.size);
@@ -1,4 +1,3 @@
1
- import nodePath from "node:path";
2
1
  import { Coder } from "./coder.js";
3
2
  import { printObject, printObjectWithoutQuotes } from "./printers.js";
4
3
  import { SchemaTypeCoder } from "./schema-type-coder.js";
@@ -40,7 +39,7 @@ export class ResponseTypeCoder extends Coder {
40
39
  if (response.has("content") || response.has("schema")) {
41
40
  return printObject(this.buildContentObjectType(script, response));
42
41
  }
43
- return "{}";
42
+ return "never";
44
43
  }
45
44
  buildHeaders(script, response) {
46
45
  return response
@@ -52,29 +51,32 @@ export class ResponseTypeCoder extends Coder {
52
51
  }
53
52
  printHeaders(script, response) {
54
53
  if (!response.has("headers")) {
55
- return "{}";
54
+ return "never";
56
55
  }
57
56
  return printObject(this.buildHeaders(script, response));
58
57
  }
58
+ printRequiredHeaders(response) {
59
+ const requiredHeaders = (response.get("headers") ?? [])
60
+ .map((value, name) => ({ name, required: value.data.required }))
61
+ .filter(({ required }) => required)
62
+ .map(({ name }) => `"${name}"`);
63
+ return requiredHeaders.length === 0 ? "never" : requiredHeaders.join(" | ");
64
+ }
59
65
  buildResponseObjectType(script) {
60
66
  return printObjectWithoutQuotes(this.requirement.map((response, responseCode) => [
61
67
  this.normalizeStatusCode(responseCode),
62
68
  `{
63
69
  headers: ${this.printHeaders(script, response)};
70
+ requiredHeaders: ${this.printRequiredHeaders(response)};
64
71
  content: ${this.printContentObjectType(script, response)};
65
72
  }`,
66
73
  ]));
67
74
  }
68
75
  write(script) {
69
- const basePath = script.path
70
- .split("/")
71
- .slice(0, -1)
72
- .map(() => "..")
73
- .join("/");
74
- script.importExternalType("ResponseBuilderFactory", nodePath.join(basePath, "types.d.ts").replaceAll("\\", "/"));
76
+ script.importSharedType("ResponseBuilderFactory");
75
77
  const text = `ResponseBuilderFactory<${this.buildResponseObjectType(script)}>`;
76
78
  if (text.includes("HttpStatusCode")) {
77
- script.importExternalType("HttpStatusCode", nodePath.join(basePath, "types.d.ts").replaceAll("\\", "/"));
79
+ script.importSharedType("HttpStatusCode");
78
80
  }
79
81
  return text;
80
82
  }
@@ -13,6 +13,13 @@ export class Script {
13
13
  this.typeCache = new Map();
14
14
  this.path = path;
15
15
  }
16
+ get relativePathToBase() {
17
+ return this.path
18
+ .split("/")
19
+ .slice(0, -1)
20
+ .map(() => "..")
21
+ .join("/");
22
+ }
16
23
  firstUniqueName(coder) {
17
24
  for (const name of coder.names()) {
18
25
  if (!this.imports.has(name) && !this.exports.has(name)) {
@@ -97,6 +104,11 @@ export class Script {
97
104
  importExternalType(name, modulePath) {
98
105
  return this.importExternal(name, modulePath, true);
99
106
  }
107
+ importSharedType(name) {
108
+ return this.importExternal(name, nodePath
109
+ .join(this.relativePathToBase, "types.d.ts")
110
+ .replaceAll("\\", "/"), true);
111
+ }
100
112
  exportType(coder) {
101
113
  return this.export(coder, true);
102
114
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "counterfact",
3
- "version": "0.37.2",
3
+ "version": "0.38.1",
4
4
  "description": "a library for building a fake REST API for testing",
5
5
  "type": "module",
6
6
  "main": "./src/server/counterfact.js",
@@ -47,7 +47,7 @@
47
47
  "@stryker-mutator/core": "8.2.6",
48
48
  "@stryker-mutator/jest-runner": "8.2.6",
49
49
  "@stryker-mutator/typescript-checker": "8.2.6",
50
- "@swc/core": "1.4.6",
50
+ "@swc/core": "1.4.8",
51
51
  "@swc/jest": "0.2.36",
52
52
  "@testing-library/dom": "9.3.4",
53
53
  "@types/jest": "29.5.12",
@@ -92,7 +92,7 @@
92
92
  "json-schema-faker": "0.5.6",
93
93
  "json-schema-ref-parser": "9.0.9",
94
94
  "jsonwebtoken": "9.0.2",
95
- "koa": "2.15.0",
95
+ "koa": "2.15.1",
96
96
  "koa-bodyparser": "4.4.1",
97
97
  "koa-proxy": "1.0.0-alpha.3",
98
98
  "koa2-swagger-ui": "5.10.0",