callman-core 1.38.0 → 1.40.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.
- package/dist/host/connections.d.ts +7 -0
- package/dist/host/connections.d.ts.map +1 -1
- package/dist/host/connections.js +8 -0
- package/dist/host/connections.js.map +1 -1
- package/dist/host/db/connectionMapping.d.ts +3 -0
- package/dist/host/db/connectionMapping.d.ts.map +1 -1
- package/dist/host/db/connectionMapping.js +3 -0
- package/dist/host/db/connectionMapping.js.map +1 -1
- package/dist/host/db/dbErrors.d.ts +9 -0
- package/dist/host/db/dbErrors.d.ts.map +1 -0
- package/dist/host/db/dbErrors.js +126 -0
- package/dist/host/db/dbErrors.js.map +1 -0
- package/dist/host/db/mongoAdapter.d.ts.map +1 -1
- package/dist/host/db/mongoAdapter.js +12 -2
- package/dist/host/db/mongoAdapter.js.map +1 -1
- package/dist/host/db/mysqlAdapter.d.ts.map +1 -1
- package/dist/host/db/mysqlAdapter.js +2 -1
- package/dist/host/db/mysqlAdapter.js.map +1 -1
- package/dist/host/db/oracleAdapter.d.ts.map +1 -1
- package/dist/host/db/oracleAdapter.js +12 -1
- package/dist/host/db/oracleAdapter.js.map +1 -1
- package/dist/host/db/oracleConnectString.d.ts.map +1 -1
- package/dist/host/db/oracleConnectString.js +13 -4
- package/dist/host/db/oracleConnectString.js.map +1 -1
- package/dist/host/db/queryExecutor.d.ts.map +1 -1
- package/dist/host/db/queryExecutor.js +6 -0
- package/dist/host/db/queryExecutor.js.map +1 -1
- package/dist/host/db/types.d.ts +16 -0
- package/dist/host/db/types.d.ts.map +1 -1
- package/dist/host/index.d.ts +1 -0
- package/dist/host/index.d.ts.map +1 -1
- package/dist/host/index.js +2 -0
- package/dist/host/index.js.map +1 -1
- package/dist/http/transportError.d.ts +15 -0
- package/dist/http/transportError.d.ts.map +1 -0
- package/dist/http/transportError.js +70 -0
- package/dist/http/transportError.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/mock-engine/auth.d.ts +29 -0
- package/dist/mock-engine/auth.d.ts.map +1 -0
- package/dist/mock-engine/auth.js +86 -0
- package/dist/mock-engine/auth.js.map +1 -0
- package/dist/mock-engine/conditions.d.ts +31 -0
- package/dist/mock-engine/conditions.d.ts.map +1 -0
- package/dist/mock-engine/conditions.js +210 -0
- package/dist/mock-engine/conditions.js.map +1 -0
- package/dist/mock-engine/constants.d.ts +28 -0
- package/dist/mock-engine/constants.d.ts.map +1 -0
- package/dist/mock-engine/constants.js +113 -0
- package/dist/mock-engine/constants.js.map +1 -0
- package/dist/mock-engine/context.d.ts +17 -0
- package/dist/mock-engine/context.d.ts.map +1 -0
- package/dist/mock-engine/context.js +92 -0
- package/dist/mock-engine/context.js.map +1 -0
- package/dist/mock-engine/data/actions.d.ts +4 -0
- package/dist/mock-engine/data/actions.d.ts.map +1 -0
- package/dist/mock-engine/data/actions.js +149 -0
- package/dist/mock-engine/data/actions.js.map +1 -0
- package/dist/mock-engine/data/query.d.ts +28 -0
- package/dist/mock-engine/data/query.d.ts.map +1 -0
- package/dist/mock-engine/data/query.js +402 -0
- package/dist/mock-engine/data/query.js.map +1 -0
- package/dist/mock-engine/data/record-codec.d.ts +28 -0
- package/dist/mock-engine/data/record-codec.d.ts.map +1 -0
- package/dist/mock-engine/data/record-codec.js +155 -0
- package/dist/mock-engine/data/record-codec.js.map +1 -0
- package/dist/mock-engine/data/resource-ops.d.ts +53 -0
- package/dist/mock-engine/data/resource-ops.d.ts.map +1 -0
- package/dist/mock-engine/data/resource-ops.js +461 -0
- package/dist/mock-engine/data/resource-ops.js.map +1 -0
- package/dist/mock-engine/data/seed.d.ts +21 -0
- package/dist/mock-engine/data/seed.d.ts.map +1 -0
- package/dist/mock-engine/data/seed.js +239 -0
- package/dist/mock-engine/data/seed.js.map +1 -0
- package/dist/mock-engine/data/validate-record.d.ts +30 -0
- package/dist/mock-engine/data/validate-record.d.ts.map +1 -0
- package/dist/mock-engine/data/validate-record.js +165 -0
- package/dist/mock-engine/data/validate-record.js.map +1 -0
- package/dist/mock-engine/definition.d.ts +31 -0
- package/dist/mock-engine/definition.d.ts.map +1 -0
- package/dist/mock-engine/definition.js +440 -0
- package/dist/mock-engine/definition.js.map +1 -0
- package/dist/mock-engine/engine.d.ts +75 -0
- package/dist/mock-engine/engine.d.ts.map +1 -0
- package/dist/mock-engine/engine.js +828 -0
- package/dist/mock-engine/engine.js.map +1 -0
- package/dist/mock-engine/faults.d.ts +38 -0
- package/dist/mock-engine/faults.d.ts.map +1 -0
- package/dist/mock-engine/faults.js +113 -0
- package/dist/mock-engine/faults.js.map +1 -0
- package/dist/mock-engine/helpers.d.ts +68 -0
- package/dist/mock-engine/helpers.d.ts.map +1 -0
- package/dist/mock-engine/helpers.js +388 -0
- package/dist/mock-engine/helpers.js.map +1 -0
- package/dist/mock-engine/import/build.d.ts +16 -0
- package/dist/mock-engine/import/build.d.ts.map +1 -0
- package/dist/mock-engine/import/build.js +435 -0
- package/dist/mock-engine/import/build.js.map +1 -0
- package/dist/mock-engine/import/index.d.ts +15 -0
- package/dist/mock-engine/import/index.d.ts.map +1 -0
- package/dist/mock-engine/import/index.js +92 -0
- package/dist/mock-engine/import/index.js.map +1 -0
- package/dist/mock-engine/import/openapi.d.ts +10 -0
- package/dist/mock-engine/import/openapi.d.ts.map +1 -0
- package/dist/mock-engine/import/openapi.js +199 -0
- package/dist/mock-engine/import/openapi.js.map +1 -0
- package/dist/mock-engine/import/paths.d.ts +16 -0
- package/dist/mock-engine/import/paths.d.ts.map +1 -0
- package/dist/mock-engine/import/paths.js +64 -0
- package/dist/mock-engine/import/paths.js.map +1 -0
- package/dist/mock-engine/import/postman.d.ts +21 -0
- package/dist/mock-engine/import/postman.d.ts.map +1 -0
- package/dist/mock-engine/import/postman.js +241 -0
- package/dist/mock-engine/import/postman.js.map +1 -0
- package/dist/mock-engine/import/report.d.ts +12 -0
- package/dist/mock-engine/import/report.d.ts.map +1 -0
- package/dist/mock-engine/import/report.js +39 -0
- package/dist/mock-engine/import/report.js.map +1 -0
- package/dist/mock-engine/import/types.d.ts +94 -0
- package/dist/mock-engine/import/types.d.ts.map +1 -0
- package/dist/mock-engine/import/types.js +6 -0
- package/dist/mock-engine/import/types.js.map +1 -0
- package/dist/mock-engine/index.d.ts +24 -0
- package/dist/mock-engine/index.d.ts.map +1 -0
- package/dist/mock-engine/index.js +27 -0
- package/dist/mock-engine/index.js.map +1 -0
- package/dist/mock-engine/legacy-builder.d.ts +27 -0
- package/dist/mock-engine/legacy-builder.d.ts.map +1 -0
- package/dist/mock-engine/legacy-builder.js +322 -0
- package/dist/mock-engine/legacy-builder.js.map +1 -0
- package/dist/mock-engine/memory-store.d.ts +73 -0
- package/dist/mock-engine/memory-store.d.ts.map +1 -0
- package/dist/mock-engine/memory-store.js +314 -0
- package/dist/mock-engine/memory-store.js.map +1 -0
- package/dist/mock-engine/node/ajv-body-validator.d.ts +13 -0
- package/dist/mock-engine/node/ajv-body-validator.d.ts.map +1 -0
- package/dist/mock-engine/node/ajv-body-validator.js +158 -0
- package/dist/mock-engine/node/ajv-body-validator.js.map +1 -0
- package/dist/mock-engine/node/file-store.d.ts +30 -0
- package/dist/mock-engine/node/file-store.d.ts.map +1 -0
- package/dist/mock-engine/node/file-store.js +116 -0
- package/dist/mock-engine/node/file-store.js.map +1 -0
- package/dist/mock-engine/node/index.d.ts +5 -0
- package/dist/mock-engine/node/index.d.ts.map +1 -0
- package/dist/mock-engine/node/index.js +8 -0
- package/dist/mock-engine/node/index.js.map +1 -0
- package/dist/mock-engine/node/node-http-adapter.d.ts +38 -0
- package/dist/mock-engine/node/node-http-adapter.d.ts.map +1 -0
- package/dist/mock-engine/node/node-http-adapter.js +215 -0
- package/dist/mock-engine/node/node-http-adapter.js.map +1 -0
- package/dist/mock-engine/node/vm-regex-runner.d.ts +14 -0
- package/dist/mock-engine/node/vm-regex-runner.d.ts.map +1 -0
- package/dist/mock-engine/node/vm-regex-runner.js +90 -0
- package/dist/mock-engine/node/vm-regex-runner.js.map +1 -0
- package/dist/mock-engine/render.d.ts +19 -0
- package/dist/mock-engine/render.d.ts.map +1 -0
- package/dist/mock-engine/render.js +57 -0
- package/dist/mock-engine/render.js.map +1 -0
- package/dist/mock-engine/routing.d.ts +48 -0
- package/dist/mock-engine/routing.d.ts.map +1 -0
- package/dist/mock-engine/routing.js +122 -0
- package/dist/mock-engine/routing.js.map +1 -0
- package/dist/mock-engine/templates.d.ts +58 -0
- package/dist/mock-engine/templates.d.ts.map +1 -0
- package/dist/mock-engine/templates.js +300 -0
- package/dist/mock-engine/templates.js.map +1 -0
- package/dist/mock-engine/testing/index.d.ts +13 -0
- package/dist/mock-engine/testing/index.d.ts.map +1 -0
- package/dist/mock-engine/testing/index.js +69 -0
- package/dist/mock-engine/testing/index.js.map +1 -0
- package/dist/mock-engine/types.d.ts +684 -0
- package/dist/mock-engine/types.d.ts.map +1 -0
- package/dist/mock-engine/types.js +7 -0
- package/dist/mock-engine/types.js.map +1 -0
- package/dist/mock-engine/util.d.ts +25 -0
- package/dist/mock-engine/util.d.ts.map +1 -0
- package/dist/mock-engine/util.js +143 -0
- package/dist/mock-engine/util.js.map +1 -0
- package/dist/openapi/index.d.ts +34 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +246 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/runner/requestRunner.d.ts.map +1 -1
- package/dist/runner/requestRunner.js +6 -1
- package/dist/runner/requestRunner.js.map +1 -1
- package/dist/script/features/random.d.ts.map +1 -1
- package/dist/script/features/random.js +3 -1
- package/dist/script/features/random.js.map +1 -1
- package/dist/script/types.d.ts +5 -0
- package/dist/script/types.d.ts.map +1 -1
- package/dist/templating/postman-generators.d.ts +5 -0
- package/dist/templating/postman-generators.d.ts.map +1 -1
- package/dist/templating/postman-generators.js +19 -14
- package/dist/templating/postman-generators.js.map +1 -1
- package/dist/templating/random-generators.d.ts.map +1 -1
- package/dist/templating/random-generators.js +3 -1
- package/dist/templating/random-generators.js.map +1 -1
- package/dist/templating/random-source.d.ts +12 -0
- package/dist/templating/random-source.d.ts.map +1 -0
- package/dist/templating/random-source.js +38 -0
- package/dist/templating/random-source.js.map +1 -0
- package/dist-cjs/host/connections.js +8 -0
- package/dist-cjs/host/connections.js.map +1 -1
- package/dist-cjs/host/db/connectionMapping.js +3 -0
- package/dist-cjs/host/db/connectionMapping.js.map +1 -1
- package/dist-cjs/host/db/dbErrors.js +130 -0
- package/dist-cjs/host/db/dbErrors.js.map +1 -0
- package/dist-cjs/host/db/mongoAdapter.js +12 -2
- package/dist-cjs/host/db/mongoAdapter.js.map +1 -1
- package/dist-cjs/host/db/mysqlAdapter.js +2 -1
- package/dist-cjs/host/db/mysqlAdapter.js.map +1 -1
- package/dist-cjs/host/db/oracleAdapter.js +12 -1
- package/dist-cjs/host/db/oracleAdapter.js.map +1 -1
- package/dist-cjs/host/db/oracleConnectString.js +13 -4
- package/dist-cjs/host/db/oracleConnectString.js.map +1 -1
- package/dist-cjs/host/db/queryExecutor.js +6 -0
- package/dist-cjs/host/db/queryExecutor.js.map +1 -1
- package/dist-cjs/host/index.js +5 -2
- package/dist-cjs/host/index.js.map +1 -1
- package/dist-cjs/http/transportError.js +75 -0
- package/dist-cjs/http/transportError.js.map +1 -0
- package/dist-cjs/index.js +4 -1
- package/dist-cjs/index.js.map +1 -1
- package/dist-cjs/mock-engine/auth.js +96 -0
- package/dist-cjs/mock-engine/auth.js.map +1 -0
- package/dist-cjs/mock-engine/conditions.js +216 -0
- package/dist-cjs/mock-engine/conditions.js.map +1 -0
- package/dist-cjs/mock-engine/constants.js +116 -0
- package/dist-cjs/mock-engine/constants.js.map +1 -0
- package/dist-cjs/mock-engine/context.js +99 -0
- package/dist-cjs/mock-engine/context.js.map +1 -0
- package/dist-cjs/mock-engine/data/actions.js +153 -0
- package/dist-cjs/mock-engine/data/actions.js.map +1 -0
- package/dist-cjs/mock-engine/data/query.js +413 -0
- package/dist-cjs/mock-engine/data/query.js.map +1 -0
- package/dist-cjs/mock-engine/data/record-codec.js +165 -0
- package/dist-cjs/mock-engine/data/record-codec.js.map +1 -0
- package/dist-cjs/mock-engine/data/resource-ops.js +476 -0
- package/dist-cjs/mock-engine/data/resource-ops.js.map +1 -0
- package/dist-cjs/mock-engine/data/seed.js +246 -0
- package/dist-cjs/mock-engine/data/seed.js.map +1 -0
- package/dist-cjs/mock-engine/data/validate-record.js +170 -0
- package/dist-cjs/mock-engine/data/validate-record.js.map +1 -0
- package/dist-cjs/mock-engine/definition.js +454 -0
- package/dist-cjs/mock-engine/definition.js.map +1 -0
- package/dist-cjs/mock-engine/engine.js +837 -0
- package/dist-cjs/mock-engine/engine.js.map +1 -0
- package/dist-cjs/mock-engine/faults.js +117 -0
- package/dist-cjs/mock-engine/faults.js.map +1 -0
- package/dist-cjs/mock-engine/helpers.js +399 -0
- package/dist-cjs/mock-engine/helpers.js.map +1 -0
- package/dist-cjs/mock-engine/import/build.js +439 -0
- package/dist-cjs/mock-engine/import/build.js.map +1 -0
- package/dist-cjs/mock-engine/import/index.js +120 -0
- package/dist-cjs/mock-engine/import/index.js.map +1 -0
- package/dist-cjs/mock-engine/import/openapi.js +203 -0
- package/dist-cjs/mock-engine/import/openapi.js.map +1 -0
- package/dist-cjs/mock-engine/import/paths.js +72 -0
- package/dist-cjs/mock-engine/import/paths.js.map +1 -0
- package/dist-cjs/mock-engine/import/postman.js +247 -0
- package/dist-cjs/mock-engine/import/postman.js.map +1 -0
- package/dist-cjs/mock-engine/import/report.js +43 -0
- package/dist-cjs/mock-engine/import/report.js.map +1 -0
- package/dist-cjs/mock-engine/import/types.js +7 -0
- package/dist-cjs/mock-engine/import/types.js.map +1 -0
- package/dist-cjs/mock-engine/index.js +81 -0
- package/dist-cjs/mock-engine/index.js.map +1 -0
- package/dist-cjs/mock-engine/legacy-builder.js +327 -0
- package/dist-cjs/mock-engine/legacy-builder.js.map +1 -0
- package/dist-cjs/mock-engine/memory-store.js +318 -0
- package/dist-cjs/mock-engine/memory-store.js.map +1 -0
- package/dist-cjs/mock-engine/node/ajv-body-validator.js +166 -0
- package/dist-cjs/mock-engine/node/ajv-body-validator.js.map +1 -0
- package/dist-cjs/mock-engine/node/file-store.js +123 -0
- package/dist-cjs/mock-engine/node/file-store.js.map +1 -0
- package/dist-cjs/mock-engine/node/index.js +16 -0
- package/dist-cjs/mock-engine/node/index.js.map +1 -0
- package/dist-cjs/mock-engine/node/node-http-adapter.js +219 -0
- package/dist-cjs/mock-engine/node/node-http-adapter.js.map +1 -0
- package/dist-cjs/mock-engine/node/vm-regex-runner.js +97 -0
- package/dist-cjs/mock-engine/node/vm-regex-runner.js.map +1 -0
- package/dist-cjs/mock-engine/render.js +64 -0
- package/dist-cjs/mock-engine/render.js.map +1 -0
- package/dist-cjs/mock-engine/routing.js +130 -0
- package/dist-cjs/mock-engine/routing.js.map +1 -0
- package/dist-cjs/mock-engine/templates.js +308 -0
- package/dist-cjs/mock-engine/templates.js.map +1 -0
- package/dist-cjs/mock-engine/testing/index.js +72 -0
- package/dist-cjs/mock-engine/testing/index.js.map +1 -0
- package/dist-cjs/mock-engine/types.js +8 -0
- package/dist-cjs/mock-engine/types.js.map +1 -0
- package/dist-cjs/mock-engine/util.js +166 -0
- package/dist-cjs/mock-engine/util.js.map +1 -0
- package/dist-cjs/openapi/index.js +257 -0
- package/dist-cjs/openapi/index.js.map +1 -0
- package/dist-cjs/runner/requestRunner.js +6 -1
- package/dist-cjs/runner/requestRunner.js.map +1 -1
- package/dist-cjs/script/features/random.js +3 -1
- package/dist-cjs/script/features/random.js.map +1 -1
- package/dist-cjs/templating/postman-generators.js +21 -15
- package/dist-cjs/templating/postman-generators.js.map +1 -1
- package/dist-cjs/templating/random-generators.js +3 -1
- package/dist-cjs/templating/random-generators.js.map +1 -1
- package/dist-cjs/templating/random-source.js +44 -0
- package/dist-cjs/templating/random-source.js.map +1 -0
- package/package.json +29 -1
|
@@ -0,0 +1,837 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// The mock engine pipeline. Hosts (backend Express, desktop node:http,
|
|
3
|
+
// renderer sandbox) handle platform gates first — per-IP fuse, lookup,
|
|
4
|
+
// 404/410, X-Mock-Token, rate limits — then call `executeMockRequest`, and
|
|
5
|
+
// carry out the returned delay/transport (respond, hang, drop).
|
|
6
|
+
//
|
|
7
|
+
// Order: session → /__callman control → route match → faults(before) →
|
|
8
|
+
// sign-in requirement → body validation → variants → execute (resource
|
|
9
|
+
// endpoint | data action | legacy builder | static/template) →
|
|
10
|
+
// faults(after) → delay. Store failures surface as 503, never as a fake 2xx.
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.generateMockSecret = exports.createMockDataManager = exports.MockDataError = exports.executeMockRequest = exports.seedPlanFor = exports.createCompiledMockCache = exports.compileMockDefinition = void 0;
|
|
13
|
+
const auth_js_1 = require("./auth.js");
|
|
14
|
+
Object.defineProperty(exports, "generateMockSecret", { enumerable: true, get: function () { return auth_js_1.generateMockSecret; } });
|
|
15
|
+
const conditions_js_1 = require("./conditions.js");
|
|
16
|
+
const constants_js_1 = require("./constants.js");
|
|
17
|
+
const context_js_1 = require("./context.js");
|
|
18
|
+
const actions_js_1 = require("./data/actions.js");
|
|
19
|
+
const query_js_1 = require("./data/query.js");
|
|
20
|
+
const record_codec_js_1 = require("./data/record-codec.js");
|
|
21
|
+
const resource_ops_js_1 = require("./data/resource-ops.js");
|
|
22
|
+
const seed_js_1 = require("./data/seed.js");
|
|
23
|
+
const faults_js_1 = require("./faults.js");
|
|
24
|
+
const legacy_builder_js_1 = require("./legacy-builder.js");
|
|
25
|
+
const render_js_1 = require("./render.js");
|
|
26
|
+
const routing_js_1 = require("./routing.js");
|
|
27
|
+
const templates_js_1 = require("./templates.js");
|
|
28
|
+
const util_js_1 = require("./util.js");
|
|
29
|
+
const compileMockDefinition = (definition) => ({
|
|
30
|
+
definition,
|
|
31
|
+
table: (0, routing_js_1.compileRouteTable)(definition),
|
|
32
|
+
});
|
|
33
|
+
exports.compileMockDefinition = compileMockDefinition;
|
|
34
|
+
/** Small LRU for compiled definitions, keyed by the host (e.g. `${mockId}:${rev}`). */
|
|
35
|
+
const createCompiledMockCache = (max = 500) => {
|
|
36
|
+
const cache = new Map();
|
|
37
|
+
return {
|
|
38
|
+
get(key, factory) {
|
|
39
|
+
const hit = cache.get(key);
|
|
40
|
+
if (hit) {
|
|
41
|
+
cache.delete(key);
|
|
42
|
+
cache.set(key, hit);
|
|
43
|
+
return hit;
|
|
44
|
+
}
|
|
45
|
+
const compiled = (0, exports.compileMockDefinition)(factory());
|
|
46
|
+
cache.set(key, compiled);
|
|
47
|
+
if (cache.size > max) {
|
|
48
|
+
const oldest = cache.keys().next().value;
|
|
49
|
+
if (oldest !== undefined)
|
|
50
|
+
cache.delete(oldest);
|
|
51
|
+
}
|
|
52
|
+
return compiled;
|
|
53
|
+
},
|
|
54
|
+
clear() {
|
|
55
|
+
cache.clear();
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
exports.createCompiledMockCache = createCompiledMockCache;
|
|
60
|
+
// ── Helpers ───────────────────────────────────────────────────────────
|
|
61
|
+
const BODY_METHODS = new Set(["POST", "PUT", "PATCH"]);
|
|
62
|
+
const STATUS_TEXT = {
|
|
63
|
+
400: "Bad Request", 401: "Unauthorized", 403: "Forbidden", 404: "Not Found", 408: "Request Timeout",
|
|
64
|
+
409: "Conflict", 418: "I'm a teapot", 422: "Unprocessable Entity", 429: "Too Many Requests",
|
|
65
|
+
500: "Internal Server Error", 502: "Bad Gateway", 503: "Service Unavailable", 504: "Gateway Timeout",
|
|
66
|
+
};
|
|
67
|
+
const delayMs = (delay, ctx) => {
|
|
68
|
+
if (!delay || delay.mode === "none")
|
|
69
|
+
return 0;
|
|
70
|
+
if (delay.mode === "fixed")
|
|
71
|
+
return Math.max(0, delay.ms);
|
|
72
|
+
const min = Math.max(0, delay.minMs);
|
|
73
|
+
const max = Math.max(min, delay.maxMs);
|
|
74
|
+
return min + Math.floor(ctx.random.next() * (max - min + 1));
|
|
75
|
+
};
|
|
76
|
+
const secretCache = new Map();
|
|
77
|
+
const signingSecret = async (definition, ctx) => {
|
|
78
|
+
if (definition.userAuth?.secret)
|
|
79
|
+
return definition.userAuth.secret;
|
|
80
|
+
// A host should always store a generated secret; this keeps old/partial
|
|
81
|
+
// definitions working with a stable per-mock fallback.
|
|
82
|
+
const cached = secretCache.get(ctx.mockId);
|
|
83
|
+
if (cached)
|
|
84
|
+
return cached;
|
|
85
|
+
const derived = (0, util_js_1.toHex)(await ctx.crypto.sha256((0, util_js_1.utf8)(`callman-mock:${ctx.mockId}`)));
|
|
86
|
+
if (secretCache.size > 1000)
|
|
87
|
+
secretCache.clear();
|
|
88
|
+
secretCache.set(ctx.mockId, derived);
|
|
89
|
+
return derived;
|
|
90
|
+
};
|
|
91
|
+
/** Wrap a store so one request cannot run more than `max` operations. */
|
|
92
|
+
const budgetStore = (store, max, trace) => {
|
|
93
|
+
const tick = () => {
|
|
94
|
+
trace.storeOps += 1;
|
|
95
|
+
if (trace.storeOps > max)
|
|
96
|
+
throw new context_js_1.MockOpBudgetExceeded();
|
|
97
|
+
};
|
|
98
|
+
return {
|
|
99
|
+
ensureNamespace: (...args) => (tick(), store.ensureNamespace(...args)),
|
|
100
|
+
resetNamespace: (...args) => (tick(), store.resetNamespace(...args)),
|
|
101
|
+
dropNamespace: (...args) => (tick(), store.dropNamespace(...args)),
|
|
102
|
+
listNamespaces: (...args) => (tick(), store.listNamespaces(...args)),
|
|
103
|
+
insert: (...args) => (tick(), store.insert(...args)),
|
|
104
|
+
get: (...args) => (tick(), store.get(...args)),
|
|
105
|
+
getMany: (...args) => (tick(), store.getMany(...args)),
|
|
106
|
+
find: (...args) => (tick(), store.find(...args)),
|
|
107
|
+
count: (...args) => (tick(), store.count(...args)),
|
|
108
|
+
countNamespace: (...args) => (tick(), store.countNamespace(...args)),
|
|
109
|
+
replace: (...args) => (tick(), store.replace(...args)),
|
|
110
|
+
delete: (...args) => (tick(), store.delete(...args)),
|
|
111
|
+
clearResource: (...args) => (tick(), store.clearResource(...args)),
|
|
112
|
+
nextSequence: (...args) => (tick(), store.nextSequence(...args)),
|
|
113
|
+
bumpSequence: (...args) => (tick(), store.bumpSequence(...args)),
|
|
114
|
+
};
|
|
115
|
+
};
|
|
116
|
+
/** Seed plan for a namespace (deterministic per mock). */
|
|
117
|
+
const seedPlanFor = (definition, ctx) => (0, seed_js_1.buildSeedPlan)(definition, { mockId: ctx.mockId, now: ctx.clock.now(), crypto: ctx.crypto, maxGenerate: ctx.limits.maxSeedGenerate });
|
|
118
|
+
exports.seedPlanFor = seedPlanFor;
|
|
119
|
+
const sessionIdleMs = (definition, ctx) => Math.max(5, Math.min(1440, definition.sessions?.idleTtlMinutes ?? Math.round(ctx.limits.sessionIdleTtlMs / 60_000))) * 60_000;
|
|
120
|
+
const resultOf = (response, trace, delay = 0, transport = { kind: "respond" }, maxDelay = 60_000) => ({ response, delayMs: Math.min(maxDelay, Math.max(0, delay)), transport, trace });
|
|
121
|
+
// ── Pipeline ──────────────────────────────────────────────────────────
|
|
122
|
+
const executeMockRequest = async (compiled, req, ctx) => {
|
|
123
|
+
const trace = { faults: [], storeOps: 0 };
|
|
124
|
+
const limits = ctx.limits;
|
|
125
|
+
const maxBody = limits.maxResponseBodyBytes;
|
|
126
|
+
const definition = compiled.definition;
|
|
127
|
+
const tpl = { random: ctx.random, now: () => ctx.clock.now() };
|
|
128
|
+
const method = req.method.toUpperCase();
|
|
129
|
+
const done = (response, delay = 0, transport = { kind: "respond" }) => {
|
|
130
|
+
if (method === "HEAD")
|
|
131
|
+
response = { ...response, body: "" };
|
|
132
|
+
return resultOf(response, trace, delay, transport, limits.maxDelayMs);
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
// 1. Session namespace
|
|
136
|
+
const sessionsEnabled = definition.sessions?.enabled !== false;
|
|
137
|
+
const rawSession = req.headers[constants_js_1.MOCK_SESSION_HEADER];
|
|
138
|
+
let sessionId = null;
|
|
139
|
+
if (sessionsEnabled && rawSession !== undefined && rawSession !== "") {
|
|
140
|
+
if (!constants_js_1.MOCK_SESSION_ID_PATTERN.test(rawSession)) {
|
|
141
|
+
trace.failureReason = "bad-session";
|
|
142
|
+
return done((0, render_js_1.jsonResponse)(400, { error: "Invalid X-Mock-Session header", hint: "Use 1-64 letters, digits, '.', '_', ':' or '-'" }, maxBody));
|
|
143
|
+
}
|
|
144
|
+
sessionId = rawSession;
|
|
145
|
+
trace.session = sessionId;
|
|
146
|
+
}
|
|
147
|
+
const scope = { mockId: ctx.mockId, ns: sessionId ? `s:${sessionId}` : "_" };
|
|
148
|
+
const store = ctx.store ? budgetStore(ctx.store, limits.maxDataOpsPerRequest, trace) : undefined;
|
|
149
|
+
let nsReady = null;
|
|
150
|
+
const ensureNamespace = () => {
|
|
151
|
+
if (!store)
|
|
152
|
+
return Promise.resolve("ok");
|
|
153
|
+
if (!nsReady) {
|
|
154
|
+
nsReady = store
|
|
155
|
+
.ensureNamespace(scope, () => (0, exports.seedPlanFor)(definition, ctx), {
|
|
156
|
+
isSession: sessionId !== null,
|
|
157
|
+
maxSessions: limits.maxSessions,
|
|
158
|
+
idleTtlMs: sessionIdleMs(definition, ctx),
|
|
159
|
+
now: ctx.clock.now(),
|
|
160
|
+
})
|
|
161
|
+
.then((outcome) => (outcome.ok ? "ok" : "session-limit"));
|
|
162
|
+
}
|
|
163
|
+
return nsReady;
|
|
164
|
+
};
|
|
165
|
+
const sessionLimit = () => {
|
|
166
|
+
trace.failureReason = "session-limit";
|
|
167
|
+
return done((0, render_js_1.jsonResponse)(429, { error: "Too many active test sessions", limit: limits.maxSessions }, maxBody, { "x-mock-limit-reason": "session-limit" }));
|
|
168
|
+
};
|
|
169
|
+
// 2. Control endpoints
|
|
170
|
+
if (req.path === constants_js_1.MOCK_CONTROL_PREFIX || req.path.startsWith(`${constants_js_1.MOCK_CONTROL_PREFIX}/`)) {
|
|
171
|
+
trace.virtual = { kind: "control" };
|
|
172
|
+
return done(await handleControl(req, definition, ctx, store, scope, sessionId, maxBody));
|
|
173
|
+
}
|
|
174
|
+
// Lazily verified sign-in state.
|
|
175
|
+
let authPromise = null;
|
|
176
|
+
const getAuth = () => {
|
|
177
|
+
if (!authPromise)
|
|
178
|
+
authPromise = resolveAuth(definition, req, ctx);
|
|
179
|
+
return authPromise;
|
|
180
|
+
};
|
|
181
|
+
const baseCondition = (params, auth) => ({
|
|
182
|
+
params,
|
|
183
|
+
query: req.query,
|
|
184
|
+
headers: req.headers,
|
|
185
|
+
body: req.body,
|
|
186
|
+
session: { id: sessionId },
|
|
187
|
+
...(auth ? { auth: { authenticated: auth.authenticated, sub: auth.sub, claims: auth.claims } } : {}),
|
|
188
|
+
});
|
|
189
|
+
// 3. Route match
|
|
190
|
+
let matched = null;
|
|
191
|
+
for (const candidate of (0, routing_js_1.matchRouteTable)(compiled.table, method, req.path)) {
|
|
192
|
+
const target = candidate.entry.target;
|
|
193
|
+
if (target.kind === "route" && target.route.match) {
|
|
194
|
+
const auth = conditionNeedsAuth(target.route.match) ? await getAuth() : undefined;
|
|
195
|
+
if (!(0, conditions_js_1.evaluateCondition)(target.route.match, baseCondition(candidate.params, auth), ctx.regex))
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
matched = candidate;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (!matched) {
|
|
202
|
+
trace.failureReason = "no-route";
|
|
203
|
+
return done((0, render_js_1.jsonResponse)(404, { error: "No matching route", hint: `Configure a route for ${method} ${req.path}` }, maxBody));
|
|
204
|
+
}
|
|
205
|
+
const { entry, params } = matched;
|
|
206
|
+
const target = entry.target;
|
|
207
|
+
// Target-specific settings
|
|
208
|
+
let counterKey = "";
|
|
209
|
+
let faultConfig;
|
|
210
|
+
let delayConfig;
|
|
211
|
+
let requirement = "none";
|
|
212
|
+
let validation;
|
|
213
|
+
const protect = definition.userAuth?.enabled ? definition.userAuth.protect : "none";
|
|
214
|
+
if (target.kind === "route") {
|
|
215
|
+
counterKey = target.route.routeId;
|
|
216
|
+
trace.routeId = target.route.routeId;
|
|
217
|
+
faultConfig = target.route.faults;
|
|
218
|
+
delayConfig = target.route.behavior?.delay;
|
|
219
|
+
requirement = target.route.auth ?? (protect === "all" ? "user" : "none");
|
|
220
|
+
validation = target.route.requestValidation;
|
|
221
|
+
}
|
|
222
|
+
else if (target.kind === "resource" || target.kind === "nested") {
|
|
223
|
+
const resource = target.kind === "resource" ? target.resource : target.child;
|
|
224
|
+
const endpointName = target.kind === "resource" ? target.endpoint : target.endpoint;
|
|
225
|
+
const endpoint = resource.endpoints[endpointName];
|
|
226
|
+
counterKey = `res:${resource.resourceId}:${endpointName}`;
|
|
227
|
+
trace.virtual = { kind: "resource", resource: resource.name, endpoint: endpointName };
|
|
228
|
+
faultConfig = endpoint.faults;
|
|
229
|
+
delayConfig = endpoint.delay;
|
|
230
|
+
requirement = endpoint.auth ?? resource.auth ?? (protect !== "none" || resource.ownerField ? "user" : "none");
|
|
231
|
+
validation = endpoint.requestValidation;
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
counterKey = `auth:${target.op}`;
|
|
235
|
+
trace.virtual = { kind: "auth", endpoint: target.op };
|
|
236
|
+
requirement = target.op === "me" ? "user" : "none";
|
|
237
|
+
}
|
|
238
|
+
const callCount = { value: null, computed: false };
|
|
239
|
+
const authForFaults = faultsNeedAuth(faultConfig, definition.faults) ? await getAuth() : undefined;
|
|
240
|
+
const faultInput = {
|
|
241
|
+
configs: [faultConfig, definition.faults],
|
|
242
|
+
counterKey,
|
|
243
|
+
scope,
|
|
244
|
+
conditionContext: baseCondition(params, authForFaults),
|
|
245
|
+
callCount,
|
|
246
|
+
};
|
|
247
|
+
// 4. Faults (before)
|
|
248
|
+
const before = await (0, faults_js_1.evaluateFaults)(ctx, { ...faultInput, phase: "before" });
|
|
249
|
+
trace.faults.push(...before.applied);
|
|
250
|
+
const baseDelay = delayMs(delayConfig, ctx);
|
|
251
|
+
if (before.terminal) {
|
|
252
|
+
return applyTerminalFault(before, baseDelay + before.extraDelayMs, req, params, sessionId, ctx, tpl, trace, done, maxBody);
|
|
253
|
+
}
|
|
254
|
+
// 5. Sign-in requirement
|
|
255
|
+
let auth;
|
|
256
|
+
if (requirement !== "none") {
|
|
257
|
+
auth = await getAuth();
|
|
258
|
+
if (!auth.authenticated) {
|
|
259
|
+
trace.failureReason = "auth-required";
|
|
260
|
+
return done((0, render_js_1.renderOutcome)(definition.userAuth?.responses?.unauthorized, {
|
|
261
|
+
statusCode: 401,
|
|
262
|
+
body: { error: "Unauthorized", message: auth.tokenError ? `Bearer token ${auth.tokenError}` : "Sign in and send Authorization: Bearer <token>" },
|
|
263
|
+
headers: { "www-authenticate": 'Bearer realm="mock"' + (auth.tokenError ? ', error="invalid_token"' : "") },
|
|
264
|
+
}, scopeFor(req, params, sessionId, ctx, auth), tpl, maxBody), baseDelay + before.extraDelayMs);
|
|
265
|
+
}
|
|
266
|
+
trace.authSubject = auth.sub;
|
|
267
|
+
if (typeof requirement === "object" && Array.isArray(requirement.roles) && requirement.roles.length > 0) {
|
|
268
|
+
const role = auth.claims?.role;
|
|
269
|
+
if (typeof role !== "string" || !requirement.roles.includes(role)) {
|
|
270
|
+
trace.failureReason = "forbidden";
|
|
271
|
+
return done((0, render_js_1.renderOutcome)(definition.userAuth?.responses?.forbidden, { statusCode: 403, body: { error: "Forbidden" } }, scopeFor(req, params, sessionId, ctx, auth), tpl, maxBody), baseDelay + before.extraDelayMs);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
else if (definition.userAuth?.enabled && (0, util_js_1.lookupHeader)(req.headers, "authorization")) {
|
|
276
|
+
auth = await getAuth();
|
|
277
|
+
if (auth.authenticated)
|
|
278
|
+
trace.authSubject = auth.sub;
|
|
279
|
+
}
|
|
280
|
+
// 6. Body validation
|
|
281
|
+
if (validation?.mode === "schema" && validation.schema !== undefined && BODY_METHODS.has(method) && ctx.bodyValidator) {
|
|
282
|
+
const invalidFields = ctx.bodyValidator(validation.schema, req.body);
|
|
283
|
+
if (invalidFields) {
|
|
284
|
+
trace.failureReason = "request-validation-failed";
|
|
285
|
+
return done((0, render_js_1.jsonResponse)(400, { error: "Request body validation failed", invalidFields }, maxBody), baseDelay + before.extraDelayMs);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
const templateScope = scopeFor(req, params, sessionId, ctx, auth);
|
|
289
|
+
if (callCount.computed && callCount.value !== null)
|
|
290
|
+
templateScope.call = { count: callCount.value };
|
|
291
|
+
const dataRuntime = () => ({
|
|
292
|
+
definition,
|
|
293
|
+
scope,
|
|
294
|
+
store: store,
|
|
295
|
+
ctx,
|
|
296
|
+
tpl,
|
|
297
|
+
templateScope,
|
|
298
|
+
mountPath: req.mountPath,
|
|
299
|
+
requestPath: req.path,
|
|
300
|
+
});
|
|
301
|
+
const needsStore = () => store ? null : (0, render_js_1.jsonResponse)(500, { error: "This mock has data endpoints but no data store is configured" }, maxBody);
|
|
302
|
+
// Load auth.user lazily when a template asks for it.
|
|
303
|
+
const loadAuthUser = async (templates) => {
|
|
304
|
+
if (!auth?.authenticated || !auth.sub || !store || !definition.userAuth?.enabled)
|
|
305
|
+
return;
|
|
306
|
+
if (!(0, templates_js_1.templateReferences)(templates, "auth.user"))
|
|
307
|
+
return;
|
|
308
|
+
const users = (0, resource_ops_js_1.resourceByName)(definition, definition.userAuth.usersResource);
|
|
309
|
+
if (!users)
|
|
310
|
+
return;
|
|
311
|
+
if ((await ensureNamespace()) === "session-limit")
|
|
312
|
+
return;
|
|
313
|
+
const doc = await store.get(scope, users.resourceId, (0, record_codec_js_1.keyFromParam)(users, auth.sub));
|
|
314
|
+
const user = doc ? (0, resource_ops_js_1.presentRecord)(users, doc) : null;
|
|
315
|
+
templateScope.auth.user = user;
|
|
316
|
+
};
|
|
317
|
+
// 7–8. Variants, then execute
|
|
318
|
+
let response;
|
|
319
|
+
if (target.kind === "route") {
|
|
320
|
+
const route = target.route;
|
|
321
|
+
let variantResponse = null;
|
|
322
|
+
if (route.variants && route.variants.length > 0) {
|
|
323
|
+
const needAuth = route.variants.some((variant) => conditionNeedsAuth(variant.condition));
|
|
324
|
+
const condAuth = needAuth ? (auth ?? (await getAuth())) : auth;
|
|
325
|
+
const condition = baseCondition(params, condAuth);
|
|
326
|
+
const ordered = route.variants.slice().sort((a, b) => a.priority - b.priority);
|
|
327
|
+
for (const variant of ordered) {
|
|
328
|
+
if ((0, conditions_js_1.evaluateCondition)(variant.condition, condition, ctx.regex)) {
|
|
329
|
+
variantResponse = variant.response;
|
|
330
|
+
trace.variantId = variant.variantId;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
if (variantResponse) {
|
|
336
|
+
response = await renderRouteResponse(variantResponse, templateScope, ctx, tpl, trace, maxBody, loadAuthUser);
|
|
337
|
+
}
|
|
338
|
+
else if (route.action) {
|
|
339
|
+
const missing = needsStore();
|
|
340
|
+
if (missing)
|
|
341
|
+
return done(missing);
|
|
342
|
+
if ((await ensureNamespace()) === "session-limit")
|
|
343
|
+
return sessionLimit();
|
|
344
|
+
const overrides = { ...(route.action.responses ?? {}) };
|
|
345
|
+
if (!overrides.success && route.response.type === "template") {
|
|
346
|
+
overrides.success = { statusCode: route.response.statusCode, headers: route.response.headers, body: route.response.body };
|
|
347
|
+
}
|
|
348
|
+
await loadAuthUser(overrides);
|
|
349
|
+
const op = await (0, actions_js_1.runDataAction)(dataRuntime(), route.action, req.rawQuery);
|
|
350
|
+
trace.action = { operation: route.action.operation, resource: route.action.resource, outcome: op.outcome, affected: op.affected };
|
|
351
|
+
response = renderOp(op, overrides, templateScope, tpl, maxBody, route.action.operation === "find" ? undefined : actionDefaults(route.action.operation, op));
|
|
352
|
+
if (op.status >= 400)
|
|
353
|
+
trace.failureReason = `data-${op.outcome}`;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
response = await renderRouteResponse(route.response, templateScope, ctx, tpl, trace, maxBody, loadAuthUser);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else if (target.kind === "resource" || target.kind === "nested") {
|
|
360
|
+
const missing = needsStore();
|
|
361
|
+
if (missing)
|
|
362
|
+
return done(missing);
|
|
363
|
+
if ((await ensureNamespace()) === "session-limit")
|
|
364
|
+
return sessionLimit();
|
|
365
|
+
const rt = dataRuntime();
|
|
366
|
+
const op = await runResourceEndpoint(rt, target, params, req);
|
|
367
|
+
const resource = target.kind === "resource" ? target.resource : target.child;
|
|
368
|
+
const endpointName = target.endpoint;
|
|
369
|
+
const overrides = resource.endpoints[endpointName].responses ?? {};
|
|
370
|
+
await loadAuthUser(overrides);
|
|
371
|
+
trace.action = { operation: endpointName, resource: resource.name, outcome: op.outcome, affected: op.affected };
|
|
372
|
+
response = renderOp(op, overrides, templateScope, tpl, maxBody);
|
|
373
|
+
if (op.status >= 400)
|
|
374
|
+
trace.failureReason = `data-${op.outcome}`;
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
const missing = needsStore();
|
|
378
|
+
if (missing)
|
|
379
|
+
return done(missing);
|
|
380
|
+
if ((await ensureNamespace()) === "session-limit")
|
|
381
|
+
return sessionLimit();
|
|
382
|
+
response = await runAuthEndpoint(target.op, dataRuntime(), req, auth, trace, maxBody);
|
|
383
|
+
}
|
|
384
|
+
// 9. Faults (after) — the handler ran; the answer is lost or delayed.
|
|
385
|
+
const after = await (0, faults_js_1.evaluateFaults)(ctx, { ...faultInput, phase: "after" });
|
|
386
|
+
trace.faults.push(...after.applied);
|
|
387
|
+
const totalDelay = baseDelay + before.extraDelayMs + after.extraDelayMs;
|
|
388
|
+
if (after.terminal)
|
|
389
|
+
return applyTerminalFault(after, totalDelay, req, params, sessionId, ctx, tpl, trace, done, maxBody);
|
|
390
|
+
return done(response, totalDelay);
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
if (error instanceof context_js_1.MockOpBudgetExceeded) {
|
|
394
|
+
trace.failureReason = "data-budget";
|
|
395
|
+
return done((0, render_js_1.jsonResponse)(500, { error: "This request needed too many data operations" }, maxBody));
|
|
396
|
+
}
|
|
397
|
+
trace.failureReason = "data-error";
|
|
398
|
+
ctx.logger?.error("Mock engine request failed", {
|
|
399
|
+
mockId: ctx.mockId,
|
|
400
|
+
path: req.path,
|
|
401
|
+
message: error instanceof Error ? error.message : String(error),
|
|
402
|
+
});
|
|
403
|
+
return done((0, render_js_1.jsonResponse)(503, { error: "Mock data store unavailable" }, maxBody));
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
exports.executeMockRequest = executeMockRequest;
|
|
407
|
+
// ── Pieces of the pipeline ────────────────────────────────────────────
|
|
408
|
+
const conditionNeedsAuth = (condition) => !!condition && condition.rules.some((rule) => rule.field === "auth" || rule.field.startsWith("auth."));
|
|
409
|
+
const faultsNeedAuth = (...configs) => configs.some((config) => config?.rules.some((rule) => rule.when?.condition && conditionNeedsAuth(rule.when.condition)));
|
|
410
|
+
const scopeFor = (req, params, sessionId, ctx, auth) => ({
|
|
411
|
+
request: {
|
|
412
|
+
method: req.method.toUpperCase(),
|
|
413
|
+
path: req.path,
|
|
414
|
+
url: `${req.mountPath}${req.path}`,
|
|
415
|
+
params,
|
|
416
|
+
query: req.query,
|
|
417
|
+
headers: req.headers,
|
|
418
|
+
body: req.body,
|
|
419
|
+
},
|
|
420
|
+
state: ctx.legacyState?.snapshot() ?? {},
|
|
421
|
+
session: { id: sessionId },
|
|
422
|
+
mock: { id: ctx.mockId, ...(ctx.mockName ? { name: ctx.mockName } : {}) },
|
|
423
|
+
auth: auth?.authenticated
|
|
424
|
+
? { authenticated: true, sub: auth.sub, claims: auth.claims ?? {} }
|
|
425
|
+
: { authenticated: false },
|
|
426
|
+
});
|
|
427
|
+
const resolveAuth = async (definition, req, ctx) => {
|
|
428
|
+
const config = definition.userAuth;
|
|
429
|
+
if (!config?.enabled)
|
|
430
|
+
return { authenticated: false };
|
|
431
|
+
const token = (0, auth_js_1.bearerToken)((0, util_js_1.lookupHeader)(req.headers, "authorization"));
|
|
432
|
+
if (!token)
|
|
433
|
+
return { authenticated: false };
|
|
434
|
+
const verified = await (0, auth_js_1.verifyMockJwt)(ctx.crypto, await signingSecret(definition, ctx), token, {
|
|
435
|
+
nowSeconds: Math.floor(ctx.clock.now() / 1000),
|
|
436
|
+
issuer: config.issuer ?? `callman-mock/${ctx.mockId}`,
|
|
437
|
+
audience: config.audience,
|
|
438
|
+
});
|
|
439
|
+
if (!verified.ok)
|
|
440
|
+
return { authenticated: false, tokenError: verified.reason === "expired" ? "has expired" : "is invalid" };
|
|
441
|
+
return { authenticated: true, sub: verified.claims.sub, claims: verified.claims };
|
|
442
|
+
};
|
|
443
|
+
const renderRouteResponse = async (config, scope, ctx, tpl, trace, maxBody, loadAuthUser) => {
|
|
444
|
+
if (config.type === "builder") {
|
|
445
|
+
const state = ctx.legacyState?.snapshot() ?? {};
|
|
446
|
+
const built = (0, legacy_builder_js_1.renderLegacyBuilder)(config, {
|
|
447
|
+
request: scope.request,
|
|
448
|
+
state,
|
|
449
|
+
random: ctx.random,
|
|
450
|
+
now: () => ctx.clock.now(),
|
|
451
|
+
maxBodyBytes: maxBody,
|
|
452
|
+
});
|
|
453
|
+
if (built.mutations && built.mutations.length > 0) {
|
|
454
|
+
if (!ctx.legacyState)
|
|
455
|
+
return (0, render_js_1.jsonResponse)(500, { error: "This mock's state cannot be saved here" }, maxBody);
|
|
456
|
+
try {
|
|
457
|
+
await ctx.legacyState.apply(built.mutations);
|
|
458
|
+
trace.legacyStateChanged = true;
|
|
459
|
+
(0, legacy_builder_js_1.applyLegacyMutationsInMemory)(state, built.mutations);
|
|
460
|
+
}
|
|
461
|
+
catch (error) {
|
|
462
|
+
trace.failureReason = "state-write-failed";
|
|
463
|
+
ctx.logger?.error("Mock state mutation failed", { mockId: ctx.mockId, message: error instanceof Error ? error.message : String(error) });
|
|
464
|
+
return (0, render_js_1.jsonResponse)(500, { error: "Mock state could not be saved" }, maxBody);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return built.rendered;
|
|
468
|
+
}
|
|
469
|
+
if (config.type === "template")
|
|
470
|
+
await loadAuthUser([config.body, config.headers]);
|
|
471
|
+
return (0, render_js_1.renderResponseConfig)(config, scope, tpl, maxBody);
|
|
472
|
+
};
|
|
473
|
+
const actionDefaults = (operation, op) => {
|
|
474
|
+
if (op.status >= 400)
|
|
475
|
+
return undefined;
|
|
476
|
+
switch (operation) {
|
|
477
|
+
case "count":
|
|
478
|
+
return { statusCode: 200, body: { count: op.scope.total ?? 0 } };
|
|
479
|
+
case "delete":
|
|
480
|
+
return { statusCode: 204, body: null };
|
|
481
|
+
default:
|
|
482
|
+
return undefined;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
const renderOp = (op, overrides, scope, tpl, maxBody, defaults) => {
|
|
486
|
+
const outcome = op.outcome;
|
|
487
|
+
const override = overrides[outcome] ?? (outcome === "created" ? overrides.success : undefined);
|
|
488
|
+
return (0, render_js_1.renderOutcome)(override, { statusCode: defaults?.statusCode ?? op.status, body: defaults?.body ?? op.body, headers: op.headers }, { ...scope, ...op.scope }, tpl, maxBody);
|
|
489
|
+
};
|
|
490
|
+
const coerceParam = (resource, field, raw) => {
|
|
491
|
+
const declared = resource.fields.find((f) => f.name === field);
|
|
492
|
+
if ((declared?.type === "integer" || declared?.type === "number") && /^-?\d+(\.\d+)?$/.test(raw))
|
|
493
|
+
return Number(raw);
|
|
494
|
+
return raw;
|
|
495
|
+
};
|
|
496
|
+
const runResourceEndpoint = async (rt, target, params, req) => {
|
|
497
|
+
const ownerStamp = (resource) => {
|
|
498
|
+
const sub = rt.templateScope.auth?.sub;
|
|
499
|
+
if (!resource.ownerField || sub === undefined)
|
|
500
|
+
return {};
|
|
501
|
+
return { [resource.ownerField]: coerceParam(resource, resource.ownerField, sub) };
|
|
502
|
+
};
|
|
503
|
+
if (target.kind === "nested") {
|
|
504
|
+
const parentValue = params[target.field] ?? "";
|
|
505
|
+
const parent = await (0, resource_ops_js_1.findByLookup)(rt, target.parent, parentValue);
|
|
506
|
+
if (!parent)
|
|
507
|
+
return (0, resource_ops_js_1.notFoundResult)(target.parent);
|
|
508
|
+
const parentId = parent.data[target.parent.idField];
|
|
509
|
+
if (target.endpoint === "list") {
|
|
510
|
+
const key = (0, record_codec_js_1.canonicalKey)(parentId);
|
|
511
|
+
return (0, resource_ops_js_1.listRecords)(rt, target.child, req.rawQuery, key ? { ref: target.field, keys: [key] } : undefined);
|
|
512
|
+
}
|
|
513
|
+
return (0, resource_ops_js_1.createRecord)(rt, target.child, req.body, { ...ownerStamp(target.child), [target.field]: parentId });
|
|
514
|
+
}
|
|
515
|
+
const { resource, endpoint } = target;
|
|
516
|
+
const lookup = resource.lookupField ?? resource.idField;
|
|
517
|
+
const value = params[lookup] ?? "";
|
|
518
|
+
switch (endpoint) {
|
|
519
|
+
case "list":
|
|
520
|
+
return (0, resource_ops_js_1.listRecords)(rt, resource, req.rawQuery);
|
|
521
|
+
case "get":
|
|
522
|
+
return (0, resource_ops_js_1.getRecord)(rt, resource, value, req.rawQuery);
|
|
523
|
+
case "create":
|
|
524
|
+
return (0, resource_ops_js_1.createRecord)(rt, resource, req.body, ownerStamp(resource));
|
|
525
|
+
case "replace":
|
|
526
|
+
case "patch": {
|
|
527
|
+
const doc = await (0, resource_ops_js_1.findByLookup)(rt, resource, value);
|
|
528
|
+
if (!doc)
|
|
529
|
+
return (0, resource_ops_js_1.notFoundResult)(resource);
|
|
530
|
+
return (0, resource_ops_js_1.writeRecord)(rt, resource, doc, req.body, endpoint === "replace" ? "replace" : "patch");
|
|
531
|
+
}
|
|
532
|
+
case "delete": {
|
|
533
|
+
const doc = await (0, resource_ops_js_1.findByLookup)(rt, resource, value);
|
|
534
|
+
return (0, resource_ops_js_1.deleteRecords)(rt, resource, doc ? [doc] : []);
|
|
535
|
+
}
|
|
536
|
+
default:
|
|
537
|
+
return (0, resource_ops_js_1.notFoundResult)(resource);
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
const issueToken = async (definition, ctx, users, user) => {
|
|
541
|
+
const config = definition.userAuth;
|
|
542
|
+
const nowSeconds = Math.floor(ctx.clock.now() / 1000);
|
|
543
|
+
const claims = {
|
|
544
|
+
sub: String(user[users.idField]),
|
|
545
|
+
iat: nowSeconds,
|
|
546
|
+
exp: nowSeconds + config.tokenTtlSeconds,
|
|
547
|
+
iss: config.issuer ?? `callman-mock/${ctx.mockId}`,
|
|
548
|
+
...(config.audience ? { aud: config.audience } : {}),
|
|
549
|
+
};
|
|
550
|
+
if (config.roleClaim && user[config.roleClaim] !== undefined)
|
|
551
|
+
claims.role = user[config.roleClaim];
|
|
552
|
+
for (const [claim, field] of Object.entries(config.claims ?? {})) {
|
|
553
|
+
if (user[field] !== undefined && !["sub", "iat", "exp", "iss", "aud"].includes(claim))
|
|
554
|
+
claims[claim] = user[field];
|
|
555
|
+
}
|
|
556
|
+
const token = await (0, auth_js_1.signMockJwt)(ctx.crypto, await signingSecret(definition, ctx), claims);
|
|
557
|
+
return { token, expiresIn: config.tokenTtlSeconds };
|
|
558
|
+
};
|
|
559
|
+
const runAuthEndpoint = async (op, rt, req, auth, trace, maxBody) => {
|
|
560
|
+
const definition = rt.definition;
|
|
561
|
+
const config = definition.userAuth;
|
|
562
|
+
const users = (0, resource_ops_js_1.resourceByName)(definition, config.usersResource);
|
|
563
|
+
if (!users)
|
|
564
|
+
return (0, render_js_1.jsonResponse)(500, { error: `Sign-in is configured for an unknown resource "${config.usersResource}"` }, maxBody);
|
|
565
|
+
const scope = rt.templateScope;
|
|
566
|
+
if (op === "me") {
|
|
567
|
+
if (!auth?.sub)
|
|
568
|
+
return (0, render_js_1.jsonResponse)(401, { error: "Unauthorized" }, maxBody);
|
|
569
|
+
const doc = await rt.store.get(rt.scope, users.resourceId, (0, record_codec_js_1.keyFromParam)(users, auth.sub));
|
|
570
|
+
if (!doc) {
|
|
571
|
+
trace.failureReason = "auth-required";
|
|
572
|
+
return (0, render_js_1.jsonResponse)(401, { error: "Unauthorized", message: "The signed-in user no longer exists" }, maxBody);
|
|
573
|
+
}
|
|
574
|
+
const user = (0, resource_ops_js_1.presentRecord)(users, doc);
|
|
575
|
+
return (0, render_js_1.buildRendered)(200, {}, user, maxBody);
|
|
576
|
+
}
|
|
577
|
+
if (op === "register") {
|
|
578
|
+
const created = await (0, resource_ops_js_1.createRecord)(rt, users, req.body);
|
|
579
|
+
if (created.outcome !== "created") {
|
|
580
|
+
trace.failureReason = `data-${created.outcome}`;
|
|
581
|
+
return (0, render_js_1.renderOutcome)(undefined, { statusCode: created.status, body: created.body }, { ...scope, ...created.scope }, rt.tpl, maxBody);
|
|
582
|
+
}
|
|
583
|
+
const user = created.scope.record;
|
|
584
|
+
const stored = await rt.store.get(rt.scope, users.resourceId, (0, record_codec_js_1.keyFromParam)(users, String(user[users.idField])));
|
|
585
|
+
const { token, expiresIn } = await issueToken(definition, rt.ctx, users, stored?.data ?? user);
|
|
586
|
+
trace.authSubject = String(user[users.idField]);
|
|
587
|
+
return (0, render_js_1.renderOutcome)(config.responses?.loginSuccess, { statusCode: 201, body: { accessToken: token, tokenType: "Bearer", expiresIn, user } }, { ...scope, token, expiresIn, record: user }, rt.tpl, maxBody);
|
|
588
|
+
}
|
|
589
|
+
// login
|
|
590
|
+
const body = (0, util_js_1.isPlainObject)(req.body) ? req.body : {};
|
|
591
|
+
const identity = body[config.identityField];
|
|
592
|
+
const password = body[config.passwordField];
|
|
593
|
+
if (typeof identity !== "string" || identity === "" || typeof password !== "string") {
|
|
594
|
+
trace.failureReason = "data-validationError";
|
|
595
|
+
const invalid = [
|
|
596
|
+
...(typeof identity !== "string" || identity === "" ? [{ field: config.identityField, message: "is required" }] : []),
|
|
597
|
+
...(typeof password !== "string" ? [{ field: config.passwordField, message: "is required" }] : []),
|
|
598
|
+
];
|
|
599
|
+
const failed = (0, resource_ops_js_1.validationFailure)(invalid);
|
|
600
|
+
return (0, render_js_1.buildRendered)(400, {}, failed.body, maxBody);
|
|
601
|
+
}
|
|
602
|
+
const identityField = users.fields.find((f) => f.name === config.identityField);
|
|
603
|
+
const filter = identityField?.uniqueCaseInsensitive
|
|
604
|
+
? { field: config.identityField, op: "like", value: identity }
|
|
605
|
+
: { field: config.identityField, op: "eq", value: identity };
|
|
606
|
+
const { docs } = await rt.store.find(rt.scope, users.resourceId, { filter: (0, query_js_1.andFilters)(filter), limit: 5, noTotal: true });
|
|
607
|
+
const candidate = docs.find((doc) => {
|
|
608
|
+
const value = doc.data[config.identityField];
|
|
609
|
+
return typeof value === "string" && (identityField?.uniqueCaseInsensitive ? value.toLowerCase() === identity.toLowerCase() : value === identity);
|
|
610
|
+
});
|
|
611
|
+
const ok = candidate ? await (0, auth_js_1.verifyPassword)(rt.ctx.crypto, candidate.data[config.passwordField], password) : false;
|
|
612
|
+
if (!candidate || !ok) {
|
|
613
|
+
trace.failureReason = "auth-failed";
|
|
614
|
+
return (0, render_js_1.renderOutcome)(config.responses?.loginFailure, { statusCode: 401, body: { error: "Invalid credentials" } }, scope, rt.tpl, maxBody);
|
|
615
|
+
}
|
|
616
|
+
const user = (0, resource_ops_js_1.presentRecord)(users, candidate);
|
|
617
|
+
const { token, expiresIn } = await issueToken(definition, rt.ctx, users, candidate.data);
|
|
618
|
+
trace.authSubject = String(candidate.data[users.idField]);
|
|
619
|
+
return (0, render_js_1.renderOutcome)(config.responses?.loginSuccess, { statusCode: 200, body: { accessToken: token, tokenType: "Bearer", expiresIn, user } }, { ...scope, token, expiresIn, record: user }, rt.tpl, maxBody);
|
|
620
|
+
};
|
|
621
|
+
const applyTerminalFault = (decision, delay, req, params, sessionId, ctx, tpl, trace, done, maxBody) => {
|
|
622
|
+
trace.failureReason = "fault-injected";
|
|
623
|
+
const terminal = decision.terminal;
|
|
624
|
+
if (terminal.kind === "timeout") {
|
|
625
|
+
return done((0, render_js_1.jsonResponse)(504, { error: "Gateway Timeout" }, maxBody), 0, { kind: "hang", ms: terminal.ms });
|
|
626
|
+
}
|
|
627
|
+
if (terminal.kind === "drop") {
|
|
628
|
+
return done((0, render_js_1.jsonResponse)(502, { error: "Connection dropped" }, maxBody), delay, { kind: "drop", mode: terminal.mode });
|
|
629
|
+
}
|
|
630
|
+
const status = terminal.response.statusCode ?? 500;
|
|
631
|
+
return done((0, render_js_1.renderOutcome)(terminal.response, { statusCode: status, body: { error: STATUS_TEXT[status] ?? "Error", injected: true } }, scopeFor(req, params, sessionId, ctx), tpl, maxBody), delay);
|
|
632
|
+
};
|
|
633
|
+
const handleControl = async (req, definition, ctx, store, scope, sessionId, maxBody) => {
|
|
634
|
+
const sub = req.path.slice(constants_js_1.MOCK_CONTROL_PREFIX.length);
|
|
635
|
+
const method = req.method.toUpperCase();
|
|
636
|
+
if (sub === "/session" || sub === "/session/reset") {
|
|
637
|
+
if (definition.sessions?.enabled === false)
|
|
638
|
+
return (0, render_js_1.jsonResponse)(404, { error: "Test sessions are turned off for this mock" }, maxBody);
|
|
639
|
+
if (!sessionId)
|
|
640
|
+
return (0, render_js_1.jsonResponse)(400, { error: "Send the X-Mock-Session header" }, maxBody);
|
|
641
|
+
if (!store)
|
|
642
|
+
return (0, render_js_1.jsonResponse)(500, { error: "No data store is configured" }, maxBody);
|
|
643
|
+
if (sub === "/session" && method === "GET") {
|
|
644
|
+
const info = (await store.listNamespaces(ctx.mockId)).find((ns) => ns.ns === scope.ns);
|
|
645
|
+
if (!info)
|
|
646
|
+
return (0, render_js_1.jsonResponse)(404, { error: "This session has no data yet" }, maxBody);
|
|
647
|
+
return (0, render_js_1.jsonResponse)(200, { id: sessionId, createdAt: info.createdAt, lastSeenAt: info.lastSeenAt, expiresAt: info.idleExpiresAt, recordCount: info.recordCount }, maxBody);
|
|
648
|
+
}
|
|
649
|
+
if (sub === "/session/reset" && method === "POST") {
|
|
650
|
+
if (definition.sessions?.allowPublicReset === false)
|
|
651
|
+
return (0, render_js_1.jsonResponse)(403, { error: "Resetting sessions from the mock URL is turned off" }, maxBody);
|
|
652
|
+
const plan = await (0, exports.seedPlanFor)(definition, ctx);
|
|
653
|
+
await store.resetNamespace(scope, plan, ctx.clock.now());
|
|
654
|
+
await ctx.counters.reset(scope);
|
|
655
|
+
return (0, render_js_1.jsonResponse)(200, { reset: true, session: sessionId }, maxBody);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
return (0, render_js_1.jsonResponse)(404, { error: "Unknown control endpoint" }, maxBody);
|
|
659
|
+
};
|
|
660
|
+
class MockDataError extends Error {
|
|
661
|
+
status;
|
|
662
|
+
code;
|
|
663
|
+
constructor(status, message, code) {
|
|
664
|
+
super(message);
|
|
665
|
+
this.status = status;
|
|
666
|
+
this.code = code;
|
|
667
|
+
this.name = "MockDataError";
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
exports.MockDataError = MockDataError;
|
|
671
|
+
const createMockDataManager = (compiled, ctx) => {
|
|
672
|
+
const definition = compiled.definition;
|
|
673
|
+
if (!ctx.store)
|
|
674
|
+
throw new MockDataError(500, "No data store is configured", "MOCK_DATA_STORE_MISSING");
|
|
675
|
+
const store = ctx.store;
|
|
676
|
+
const tpl = { random: ctx.random, now: () => ctx.clock.now() };
|
|
677
|
+
const scopeOf = (session) => {
|
|
678
|
+
if (session && !constants_js_1.MOCK_SESSION_ID_PATTERN.test(session))
|
|
679
|
+
throw new MockDataError(400, "Invalid session id", "MOCK_SESSION_INVALID");
|
|
680
|
+
return { mockId: ctx.mockId, ns: session ? `s:${session}` : "_" };
|
|
681
|
+
};
|
|
682
|
+
const resourceOf = (name) => {
|
|
683
|
+
const resource = (0, resource_ops_js_1.resourceByName)(definition, name);
|
|
684
|
+
if (!resource)
|
|
685
|
+
throw new MockDataError(404, `Unknown resource "${name}"`, "MOCK_RESOURCE_NOT_FOUND");
|
|
686
|
+
return resource;
|
|
687
|
+
};
|
|
688
|
+
const runtime = async (session) => {
|
|
689
|
+
const scope = scopeOf(session);
|
|
690
|
+
const ensured = await store.ensureNamespace(scope, () => (0, exports.seedPlanFor)(definition, ctx), {
|
|
691
|
+
isSession: Boolean(session),
|
|
692
|
+
maxSessions: ctx.limits.maxSessions,
|
|
693
|
+
idleTtlMs: sessionIdleMs(definition, ctx),
|
|
694
|
+
now: ctx.clock.now(),
|
|
695
|
+
});
|
|
696
|
+
if (!ensured.ok)
|
|
697
|
+
throw new MockDataError(429, "Too many active test sessions", "MOCK_SESSION_LIMIT");
|
|
698
|
+
return {
|
|
699
|
+
definition,
|
|
700
|
+
scope,
|
|
701
|
+
store,
|
|
702
|
+
ctx,
|
|
703
|
+
tpl,
|
|
704
|
+
templateScope: { request: { method: "POST", params: {}, query: {}, headers: {} }, session: { id: session ?? null }, mock: { id: ctx.mockId } },
|
|
705
|
+
management: true,
|
|
706
|
+
mountPath: "",
|
|
707
|
+
requestPath: "",
|
|
708
|
+
};
|
|
709
|
+
};
|
|
710
|
+
return {
|
|
711
|
+
async listRecords(name, options = {}) {
|
|
712
|
+
const resource = resourceOf(name);
|
|
713
|
+
const rt = await runtime(options.session);
|
|
714
|
+
const query = options.rawQuery ?? [];
|
|
715
|
+
const hasLimit = query.some(([key]) => key === "_limit" || key === "_per_page");
|
|
716
|
+
const effective = hasLimit ? query : [...query, ["_limit", "50"]];
|
|
717
|
+
const op = await (0, resource_ops_js_1.listRecords)(rt, { ...resource, list: { ...resource.list, envelope: "array", filterMode: "any" } }, effective);
|
|
718
|
+
return {
|
|
719
|
+
items: op.scope.records ?? [],
|
|
720
|
+
total: Number(op.scope.total ?? 0),
|
|
721
|
+
page: Number(op.scope.page ?? 1),
|
|
722
|
+
limit: Number(op.scope.limit ?? 50),
|
|
723
|
+
};
|
|
724
|
+
},
|
|
725
|
+
async getRecord(name, id, session) {
|
|
726
|
+
const resource = resourceOf(name);
|
|
727
|
+
const rt = await runtime(session);
|
|
728
|
+
const doc = await rt.store.get(rt.scope, resource.resourceId, (0, record_codec_js_1.keyFromParam)(resource, id));
|
|
729
|
+
return doc ? doc.data : null;
|
|
730
|
+
},
|
|
731
|
+
async insertRecords(name, records, session) {
|
|
732
|
+
const resource = resourceOf(name);
|
|
733
|
+
const rt = await runtime(session);
|
|
734
|
+
const inserted = [];
|
|
735
|
+
const errors = [];
|
|
736
|
+
for (let i = 0; i < records.length; i += 1) {
|
|
737
|
+
const op = await (0, resource_ops_js_1.createRecord)(rt, resource, records[i]);
|
|
738
|
+
if (op.outcome === "created")
|
|
739
|
+
inserted.push(op.body);
|
|
740
|
+
else
|
|
741
|
+
errors.push({ index: i, status: op.status, body: op.body });
|
|
742
|
+
}
|
|
743
|
+
return { inserted, errors };
|
|
744
|
+
},
|
|
745
|
+
async updateRecord(name, id, body, mode, session) {
|
|
746
|
+
const resource = resourceOf(name);
|
|
747
|
+
const rt = await runtime(session);
|
|
748
|
+
const doc = await rt.store.get(rt.scope, resource.resourceId, (0, record_codec_js_1.keyFromParam)(resource, id));
|
|
749
|
+
if (!doc)
|
|
750
|
+
return { status: 404, body: { error: "Not found" } };
|
|
751
|
+
const op = await (0, resource_ops_js_1.writeRecord)(rt, resource, doc, body, mode);
|
|
752
|
+
return { status: op.status, body: op.body };
|
|
753
|
+
},
|
|
754
|
+
async deleteRecords(name, ids, session) {
|
|
755
|
+
const resource = resourceOf(name);
|
|
756
|
+
const rt = await runtime(session);
|
|
757
|
+
if (ids === "all") {
|
|
758
|
+
const deleted = await rt.store.clearResource(rt.scope, resource.resourceId);
|
|
759
|
+
return { status: 200, deleted };
|
|
760
|
+
}
|
|
761
|
+
const docs = await rt.store.getMany(rt.scope, resource.resourceId, ids.map((id) => (0, record_codec_js_1.keyFromParam)(resource, id)));
|
|
762
|
+
if (docs.length === 0)
|
|
763
|
+
return { status: 404, deleted: 0, body: { error: "Not found" } };
|
|
764
|
+
const op = await (0, resource_ops_js_1.deleteRecords)(rt, resource, docs);
|
|
765
|
+
return { status: op.status, deleted: op.affected, ...(op.status >= 400 ? { body: op.body } : {}) };
|
|
766
|
+
},
|
|
767
|
+
async resetData(session) {
|
|
768
|
+
const scope = scopeOf(session);
|
|
769
|
+
const plan = await (0, exports.seedPlanFor)(definition, ctx);
|
|
770
|
+
await store.resetNamespace(scope, plan, ctx.clock.now());
|
|
771
|
+
await ctx.counters.reset(scope);
|
|
772
|
+
},
|
|
773
|
+
async prepareData(addedResourceIds = []) {
|
|
774
|
+
if (definition.resources.length === 0)
|
|
775
|
+
return;
|
|
776
|
+
const shared = scopeOf(null);
|
|
777
|
+
const ensured = await store.ensureNamespace(shared, () => (0, exports.seedPlanFor)(definition, ctx), {
|
|
778
|
+
isSession: false,
|
|
779
|
+
maxSessions: ctx.limits.maxSessions,
|
|
780
|
+
idleTtlMs: sessionIdleMs(definition, ctx),
|
|
781
|
+
now: ctx.clock.now(),
|
|
782
|
+
});
|
|
783
|
+
const added = addedResourceIds.filter((id) => definition.resources.some((resource) => resource.resourceId === id));
|
|
784
|
+
if ((ensured.ok && ensured.created) || added.length === 0)
|
|
785
|
+
return;
|
|
786
|
+
const plan = await (0, exports.seedPlanFor)(definition, ctx);
|
|
787
|
+
const sessions = (await store.listNamespaces(ctx.mockId)).filter((ns) => ns.kind === "session" && ns.sessionId).map((ns) => scopeOf(ns.sessionId));
|
|
788
|
+
for (const scope of [shared, ...sessions]) {
|
|
789
|
+
for (const resourceId of added) {
|
|
790
|
+
if ((await store.count(scope, resourceId, undefined, 1)) > 0)
|
|
791
|
+
continue;
|
|
792
|
+
for (const doc of plan.docs) {
|
|
793
|
+
if (doc.resource === resourceId)
|
|
794
|
+
await store.insert(scope, { ...doc, data: { ...doc.data } });
|
|
795
|
+
}
|
|
796
|
+
const sequence = plan.sequences[resourceId];
|
|
797
|
+
if (sequence)
|
|
798
|
+
await store.bumpSequence(scope, resourceId, sequence);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
},
|
|
802
|
+
async listSessions() {
|
|
803
|
+
const namespaces = await store.listNamespaces(ctx.mockId);
|
|
804
|
+
return namespaces
|
|
805
|
+
.filter((ns) => ns.kind === "session" && ns.sessionId)
|
|
806
|
+
.map((ns) => ({ id: ns.sessionId, createdAt: ns.createdAt, lastSeenAt: ns.lastSeenAt, expiresAt: ns.idleExpiresAt, recordCount: ns.recordCount }));
|
|
807
|
+
},
|
|
808
|
+
async dropSession(session) {
|
|
809
|
+
const scope = scopeOf(session);
|
|
810
|
+
await store.dropNamespace(scope);
|
|
811
|
+
await ctx.counters.reset(scope);
|
|
812
|
+
},
|
|
813
|
+
async exportData(session) {
|
|
814
|
+
const rt = await runtime(session);
|
|
815
|
+
const out = {};
|
|
816
|
+
for (const resource of definition.resources) {
|
|
817
|
+
const { docs } = await rt.store.find(rt.scope, resource.resourceId, { limit: ctx.limits.maxRecordsPerResource, noTotal: true });
|
|
818
|
+
out[resource.name] = docs.map((doc) => doc.data);
|
|
819
|
+
}
|
|
820
|
+
return out;
|
|
821
|
+
},
|
|
822
|
+
async issueUserToken(userId, session) {
|
|
823
|
+
if (!definition.userAuth?.enabled)
|
|
824
|
+
return null;
|
|
825
|
+
const users = (0, resource_ops_js_1.resourceByName)(definition, definition.userAuth.usersResource);
|
|
826
|
+
if (!users)
|
|
827
|
+
return null;
|
|
828
|
+
const rt = await runtime(session);
|
|
829
|
+
const doc = await rt.store.get(rt.scope, users.resourceId, (0, record_codec_js_1.keyFromParam)(users, userId));
|
|
830
|
+
if (!doc)
|
|
831
|
+
return null;
|
|
832
|
+
return issueToken(definition, ctx, users, doc.data);
|
|
833
|
+
},
|
|
834
|
+
};
|
|
835
|
+
};
|
|
836
|
+
exports.createMockDataManager = createMockDataManager;
|
|
837
|
+
//# sourceMappingURL=engine.js.map
|