counterfact 1.2.0 → 1.3.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.
|
@@ -62,14 +62,6 @@ export class Dispatcher {
|
|
|
62
62
|
return operation;
|
|
63
63
|
}
|
|
64
64
|
normalizeResponse(response, acceptHeader) {
|
|
65
|
-
if (typeof response === "string") {
|
|
66
|
-
return {
|
|
67
|
-
body: response,
|
|
68
|
-
contentType: "text/plain",
|
|
69
|
-
headers: {},
|
|
70
|
-
status: 200,
|
|
71
|
-
};
|
|
72
|
-
}
|
|
73
65
|
if (response.content !== undefined) {
|
|
74
66
|
const content = this.selectContent(acceptHeader, response.content);
|
|
75
67
|
if (content === undefined) {
|
|
@@ -89,7 +81,9 @@ export class Dispatcher {
|
|
|
89
81
|
}
|
|
90
82
|
return {
|
|
91
83
|
...response,
|
|
92
|
-
contentType: response.headers?.["content-type"]?.toString() ??
|
|
84
|
+
contentType: response.headers?.["content-type"]?.toString() ??
|
|
85
|
+
response.contentType ??
|
|
86
|
+
"unknown/unknown",
|
|
93
87
|
};
|
|
94
88
|
}
|
|
95
89
|
selectContent(acceptHeader, content) {
|
|
@@ -15,6 +15,11 @@ const debug = createDebug("counterfact:server:module-loader");
|
|
|
15
15
|
function isContextModule(module) {
|
|
16
16
|
return "Context" in module && typeof module.Context === "function";
|
|
17
17
|
}
|
|
18
|
+
function isMiddlewareModule(module) {
|
|
19
|
+
return ("middleware" in module &&
|
|
20
|
+
typeof Object.getOwnPropertyDescriptor(module, "middleware")?.value ===
|
|
21
|
+
"function");
|
|
22
|
+
}
|
|
18
23
|
export class ModuleLoader extends EventTarget {
|
|
19
24
|
basePath;
|
|
20
25
|
registry;
|
|
@@ -96,23 +101,27 @@ export class ModuleLoader extends EventTarget {
|
|
|
96
101
|
const doImport = (await determineModuleKind(pathName)) === "commonjs"
|
|
97
102
|
? uncachedRequire
|
|
98
103
|
: uncachedImport;
|
|
99
|
-
const endpoint = (await doImport(pathName))
|
|
104
|
+
const endpoint = (await doImport(pathName).catch((err) => {
|
|
105
|
+
console.log("ERROR");
|
|
106
|
+
}));
|
|
100
107
|
this.dispatchEvent(new Event("add"));
|
|
101
|
-
if (basename(pathName).startsWith("_.context")
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
108
|
+
if (basename(pathName).startsWith("_.context.") &&
|
|
109
|
+
isContextModule(endpoint)) {
|
|
110
|
+
const loadContext = (path) => this.contextRegistry.find(path);
|
|
111
|
+
this.contextRegistry.update(directory,
|
|
112
|
+
// @ts-expect-error TS says Context has no constructable signatures but that's not true?
|
|
113
|
+
new endpoint.Context({
|
|
114
|
+
loadContext,
|
|
115
|
+
}));
|
|
116
|
+
return;
|
|
110
117
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
this.registry.add(url, endpoint);
|
|
118
|
+
if (basename(pathName).startsWith("_.middleware.") &&
|
|
119
|
+
isMiddlewareModule(endpoint)) {
|
|
120
|
+
this.registry.addMiddleware(url.slice(0, url.lastIndexOf("/")) || "/", endpoint.middleware);
|
|
115
121
|
}
|
|
122
|
+
if (url === "/index")
|
|
123
|
+
this.registry.add("/", endpoint);
|
|
124
|
+
this.registry.add(url, endpoint);
|
|
116
125
|
}
|
|
117
126
|
catch (error) {
|
|
118
127
|
if (String(error) ===
|
|
@@ -120,8 +129,8 @@ export class ModuleLoader extends EventTarget {
|
|
|
120
129
|
// Not sure why Node throws this error. It doesn't seem to matter.
|
|
121
130
|
return;
|
|
122
131
|
}
|
|
123
|
-
throw error;
|
|
124
132
|
process.stdout.write(`\nError loading ${pathName}:\n${String(error)}\n`);
|
|
133
|
+
throw error;
|
|
125
134
|
}
|
|
126
135
|
}
|
|
127
136
|
}
|
|
@@ -9,31 +9,38 @@ export class ModuleTree {
|
|
|
9
9
|
name: "",
|
|
10
10
|
rawName: "",
|
|
11
11
|
};
|
|
12
|
-
|
|
13
|
-
if (directory === undefined) {
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
12
|
+
putDirectory(directory, segments) {
|
|
16
13
|
const [segment, ...remainingSegments] = segments;
|
|
17
14
|
if (segment === undefined) {
|
|
18
15
|
throw new Error("segments array is empty");
|
|
19
16
|
}
|
|
20
17
|
if (remainingSegments.length === 0) {
|
|
21
|
-
directory
|
|
22
|
-
isWildcard: segment.startsWith("{"),
|
|
23
|
-
module,
|
|
24
|
-
name: segment.replace(/^\{(?<name>.*)\}$/u, "$<name>"),
|
|
25
|
-
rawName: segment,
|
|
26
|
-
};
|
|
27
|
-
return;
|
|
18
|
+
return directory;
|
|
28
19
|
}
|
|
29
|
-
directory.directories[segment.toLowerCase()] ??= {
|
|
20
|
+
const nextDirectory = (directory.directories[segment.toLowerCase()] ??= {
|
|
30
21
|
directories: {},
|
|
31
22
|
files: {},
|
|
32
23
|
isWildcard: segment.startsWith("{"),
|
|
33
24
|
name: segment.replace(/^\{(?<name>.*)\}$/u, "$<name>"),
|
|
34
25
|
rawName: segment,
|
|
26
|
+
});
|
|
27
|
+
return this.putDirectory(nextDirectory, remainingSegments);
|
|
28
|
+
}
|
|
29
|
+
addModuleToDirectory(directory, segments, module) {
|
|
30
|
+
if (directory === undefined) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const targetDirectory = this.putDirectory(directory, segments);
|
|
34
|
+
const filename = segments.at(-1);
|
|
35
|
+
if (filename === undefined) {
|
|
36
|
+
throw new Error("The file name (the last segment of the URL) is undefined. This is theoretically impossible but TypeScript can't enforce it.");
|
|
37
|
+
}
|
|
38
|
+
targetDirectory.files[filename] = {
|
|
39
|
+
isWildcard: filename.startsWith("{"),
|
|
40
|
+
module,
|
|
41
|
+
name: filename.replace(/^\{(?<name>.*)\}$/u, "$<name>"),
|
|
42
|
+
rawName: filename,
|
|
35
43
|
};
|
|
36
|
-
this.addModuleToDirectory(directory.directories[segment.toLocaleLowerCase()], remainingSegments, module);
|
|
37
44
|
}
|
|
38
45
|
add(url, module) {
|
|
39
46
|
this.addModuleToDirectory(this.root, url.split("/").slice(1), module);
|
package/dist/server/registry.js
CHANGED
|
@@ -25,12 +25,19 @@ function castParameters(parameters = {}, parameterTypes = {}) {
|
|
|
25
25
|
}
|
|
26
26
|
export class Registry {
|
|
27
27
|
moduleTree = new ModuleTree();
|
|
28
|
+
middlewares = new Map();
|
|
29
|
+
constructor() {
|
|
30
|
+
this.middlewares.set("/", ($, respondTo) => respondTo($));
|
|
31
|
+
}
|
|
28
32
|
get routes() {
|
|
29
33
|
return this.moduleTree.routes;
|
|
30
34
|
}
|
|
31
35
|
add(url, module) {
|
|
32
36
|
this.moduleTree.add(url, module);
|
|
33
37
|
}
|
|
38
|
+
addMiddleware(url, callback) {
|
|
39
|
+
this.middlewares.set(url, callback);
|
|
40
|
+
}
|
|
34
41
|
remove(url) {
|
|
35
42
|
this.moduleTree.remove(url);
|
|
36
43
|
}
|
|
@@ -66,7 +73,37 @@ export class Registry {
|
|
|
66
73
|
query: castParameters(requestData.query, parameterTypes.query),
|
|
67
74
|
};
|
|
68
75
|
operationArgument.x = operationArgument;
|
|
69
|
-
|
|
76
|
+
const executeAndNormalizeResponse = async (requestData) => {
|
|
77
|
+
const result = await execute(requestData);
|
|
78
|
+
if (typeof result === "string") {
|
|
79
|
+
return {
|
|
80
|
+
headers: {},
|
|
81
|
+
status: 200,
|
|
82
|
+
body: result,
|
|
83
|
+
contentType: "text/plain",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (typeof result === "undefined") {
|
|
87
|
+
return {
|
|
88
|
+
headers: {},
|
|
89
|
+
body: `The ${httpRequestMethod} function did not return anything. Did you forget a return statement?`,
|
|
90
|
+
status: 500,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
};
|
|
95
|
+
const middlewares = this.middlewares;
|
|
96
|
+
function recurse(path, respondTo) {
|
|
97
|
+
if (path === null)
|
|
98
|
+
return respondTo;
|
|
99
|
+
const nextPath = path === "" ? null : path.slice(0, path.lastIndexOf("/"));
|
|
100
|
+
const middleware = middlewares.get(path);
|
|
101
|
+
if (middleware !== undefined) {
|
|
102
|
+
return recurse(nextPath, ($) => middleware($, respondTo));
|
|
103
|
+
}
|
|
104
|
+
return recurse(nextPath, respondTo);
|
|
105
|
+
}
|
|
106
|
+
return recurse(operationArgument.matchedPath ?? "/", executeAndNormalizeResponse)(operationArgument);
|
|
70
107
|
};
|
|
71
108
|
}
|
|
72
109
|
}
|
|
@@ -55,7 +55,7 @@ export function createResponseBuilder(operation, config) {
|
|
|
55
55
|
return {
|
|
56
56
|
...this,
|
|
57
57
|
content: [
|
|
58
|
-
...(this.content ?? []),
|
|
58
|
+
...(this.content ?? []).filter((response) => response.type !== contentType),
|
|
59
59
|
{
|
|
60
60
|
body: convertToXmlIfNecessary(contentType, body, operation.responses[this.status ?? "default"]?.content?.[contentType]?.schema),
|
|
61
61
|
type: contentType,
|
package/dist/server/types.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "counterfact",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "a library for building a fake REST API for testing",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/counterfact.js",
|
|
@@ -44,11 +44,11 @@
|
|
|
44
44
|
"postinstall": "patch-package"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@changesets/cli": "2.
|
|
47
|
+
"@changesets/cli": "2.29.2",
|
|
48
48
|
"@stryker-mutator/core": "8.7.1",
|
|
49
49
|
"@stryker-mutator/jest-runner": "8.7.1",
|
|
50
50
|
"@stryker-mutator/typescript-checker": "8.7.1",
|
|
51
|
-
"@swc/core": "1.
|
|
51
|
+
"@swc/core": "1.11.21",
|
|
52
52
|
"@swc/jest": "0.2.37",
|
|
53
53
|
"@testing-library/dom": "10.4.0",
|
|
54
54
|
"@types/jest": "29.5.14",
|
|
@@ -57,19 +57,19 @@
|
|
|
57
57
|
"@types/koa-bodyparser": "4.3.12",
|
|
58
58
|
"@types/koa-proxy": "1.0.7",
|
|
59
59
|
"@types/koa-static": "4.0.4",
|
|
60
|
-
"@types/lodash": "4.17.
|
|
60
|
+
"@types/lodash": "4.17.16",
|
|
61
61
|
"copyfiles": "2.4.1",
|
|
62
|
-
"eslint": "9.
|
|
62
|
+
"eslint": "9.25.0",
|
|
63
63
|
"eslint-config-hardcore": "47.0.1",
|
|
64
64
|
"eslint-formatter-github-annotations": "0.1.0",
|
|
65
|
-
"eslint-import-resolver-typescript": "3.
|
|
65
|
+
"eslint-import-resolver-typescript": "4.3.2",
|
|
66
66
|
"eslint-plugin-etc": "2.0.3",
|
|
67
|
-
"eslint-plugin-file-progress": "3.0.
|
|
67
|
+
"eslint-plugin-file-progress": "3.0.2",
|
|
68
68
|
"eslint-plugin-import": "2.31.0",
|
|
69
69
|
"eslint-plugin-jest": "28.11.0",
|
|
70
70
|
"eslint-plugin-jest-dom": "5.5.0",
|
|
71
71
|
"eslint-plugin-no-explicit-type-exports": "0.12.1",
|
|
72
|
-
"eslint-plugin-prettier": "5.2.
|
|
72
|
+
"eslint-plugin-prettier": "5.2.6",
|
|
73
73
|
"eslint-plugin-unused-imports": "4.1.4",
|
|
74
74
|
"husky": "9.1.7",
|
|
75
75
|
"jest": "29.7.0",
|
|
@@ -77,11 +77,11 @@
|
|
|
77
77
|
"node-mocks-http": "1.16.2",
|
|
78
78
|
"rimraf": "6.0.1",
|
|
79
79
|
"stryker-cli": "1.0.2",
|
|
80
|
-
"supertest": "7.
|
|
80
|
+
"supertest": "7.1.0",
|
|
81
81
|
"using-temporary-files": "2.2.1"
|
|
82
82
|
},
|
|
83
83
|
"dependencies": {
|
|
84
|
-
"@apidevtools/json-schema-ref-parser": "
|
|
84
|
+
"@apidevtools/json-schema-ref-parser": "12.0.1",
|
|
85
85
|
"@hapi/accept": "6.0.3",
|
|
86
86
|
"@types/json-schema": "7.0.15",
|
|
87
87
|
"ast-types": "0.14.2",
|
|
@@ -93,19 +93,19 @@
|
|
|
93
93
|
"handlebars": "4.7.8",
|
|
94
94
|
"http-terminator": "3.2.0",
|
|
95
95
|
"js-yaml": "4.1.0",
|
|
96
|
-
"json-schema-faker": "0.5.
|
|
96
|
+
"json-schema-faker": "0.5.9",
|
|
97
97
|
"jsonwebtoken": "9.0.2",
|
|
98
|
-
"koa": "2.
|
|
98
|
+
"koa": "2.16.1",
|
|
99
99
|
"koa-bodyparser": "4.4.1",
|
|
100
100
|
"koa-proxies": "0.12.4",
|
|
101
101
|
"koa2-swagger-ui": "5.11.0",
|
|
102
102
|
"lodash": "4.17.21",
|
|
103
103
|
"node-fetch": "3.3.2",
|
|
104
|
-
"open": "10.1.
|
|
104
|
+
"open": "10.1.1",
|
|
105
105
|
"patch-package": "8.0.0",
|
|
106
|
-
"precinct": "12.
|
|
107
|
-
"prettier": "3.
|
|
108
|
-
"recast": "0.23.
|
|
109
|
-
"typescript": "5.
|
|
106
|
+
"precinct": "12.2.0",
|
|
107
|
+
"prettier": "3.5.3",
|
|
108
|
+
"recast": "0.23.11",
|
|
109
|
+
"typescript": "5.8.3"
|
|
110
110
|
}
|
|
111
111
|
}
|