@solidxai/core 0.1.2 → 0.1.4
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/commands/run-tests.command.d.ts +37 -0
- package/dist/commands/run-tests.command.d.ts.map +1 -0
- package/dist/commands/run-tests.command.js +345 -0
- package/dist/commands/run-tests.command.js.map +1 -0
- package/dist/commands/test-data.command.d.ts +6 -6
- package/dist/commands/test-data.command.d.ts.map +1 -1
- package/dist/commands/test-data.command.js +25 -25
- package/dist/commands/test-data.command.js.map +1 -1
- package/dist/commands/test.command.d.ts +5 -0
- package/dist/commands/test.command.d.ts.map +1 -0
- package/dist/commands/test.command.js +26 -0
- package/dist/commands/test.command.js.map +1 -0
- package/dist/controllers/service.controller.d.ts +0 -9
- package/dist/controllers/service.controller.d.ts.map +1 -1
- package/dist/controllers/service.controller.js +0 -45
- package/dist/controllers/service.controller.js.map +1 -1
- package/dist/dtos/basic-filters.dto.d.ts.map +1 -1
- package/dist/dtos/basic-filters.dto.js.map +1 -1
- package/dist/dtos/create-user.dto.d.ts +1 -0
- package/dist/dtos/create-user.dto.d.ts.map +1 -1
- package/dist/dtos/create-user.dto.js +2 -1
- package/dist/dtos/create-user.dto.js.map +1 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.d.ts.map +1 -1
- package/dist/seeders/module-metadata-seeder.service.js +1 -20
- package/dist/seeders/module-metadata-seeder.service.js.map +1 -1
- package/dist/seeders/module-test-data.service.d.ts.map +1 -1
- package/dist/seeders/module-test-data.service.js +3 -3
- package/dist/seeders/module-test-data.service.js.map +1 -1
- package/dist/services/chatter-message.service.d.ts +2 -0
- package/dist/services/chatter-message.service.d.ts.map +1 -1
- package/dist/services/chatter-message.service.js +18 -2
- package/dist/services/chatter-message.service.js.map +1 -1
- package/dist/services/crud.service.d.ts.map +1 -1
- package/dist/services/crud.service.js.map +1 -1
- package/dist/services/queues/common.d.ts +3 -0
- package/dist/services/queues/common.d.ts.map +1 -0
- package/dist/services/queues/common.js +39 -0
- package/dist/services/queues/common.js.map +1 -0
- package/dist/services/queues/database-publisher.service.d.ts.map +1 -1
- package/dist/services/queues/database-publisher.service.js +3 -1
- package/dist/services/queues/database-publisher.service.js.map +1 -1
- package/dist/services/queues/database-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/database-subscriber.service.js +5 -2
- package/dist/services/queues/database-subscriber.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-publisher.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-publisher.service.js +13 -6
- package/dist/services/queues/rabbitmq-publisher.service.js.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.d.ts.map +1 -1
- package/dist/services/queues/rabbitmq-subscriber.service.js +9 -5
- package/dist/services/queues/rabbitmq-subscriber.service.js.map +1 -1
- package/dist/solid-core.module.d.ts.map +1 -1
- package/dist/solid-core.module.js +4 -0
- package/dist/solid-core.module.js.map +1 -1
- package/dist/testing/__examples__/register-example-specs.d.ts +3 -0
- package/dist/testing/__examples__/register-example-specs.d.ts.map +1 -0
- package/dist/testing/__examples__/register-example-specs.js +8 -0
- package/dist/testing/__examples__/register-example-specs.js.map +1 -0
- package/dist/testing/__examples__/specs/custom-health.spec.d.ts +17 -0
- package/dist/testing/__examples__/specs/custom-health.spec.d.ts.map +1 -0
- package/dist/testing/__examples__/specs/custom-health.spec.js +30 -0
- package/dist/testing/__examples__/specs/custom-health.spec.js.map +1 -0
- package/dist/testing/adapters/api/api-adapter.d.ts +9 -0
- package/dist/testing/adapters/api/api-adapter.d.ts.map +1 -0
- package/dist/testing/adapters/api/api-adapter.js +76 -0
- package/dist/testing/adapters/api/api-adapter.js.map +1 -0
- package/dist/testing/adapters/api/api.types.d.ts +14 -0
- package/dist/testing/adapters/api/api.types.d.ts.map +1 -0
- package/dist/testing/adapters/api/api.types.js +3 -0
- package/dist/testing/adapters/api/api.types.js.map +1 -0
- package/dist/testing/adapters/ui/playwright-adapter.d.ts +14 -0
- package/dist/testing/adapters/ui/playwright-adapter.d.ts.map +1 -0
- package/dist/testing/adapters/ui/playwright-adapter.js +47 -0
- package/dist/testing/adapters/ui/playwright-adapter.js.map +1 -0
- package/dist/testing/adapters/ui/ui.types.d.ts +5 -0
- package/dist/testing/adapters/ui/ui.types.d.ts.map +1 -0
- package/dist/testing/adapters/ui/ui.types.js +3 -0
- package/dist/testing/adapters/ui/ui.types.js.map +1 -0
- package/dist/testing/contracts/runtime-context.types.d.ts +35 -0
- package/dist/testing/contracts/runtime-context.types.d.ts.map +1 -0
- package/dist/testing/contracts/runtime-context.types.js +3 -0
- package/dist/testing/contracts/runtime-context.types.js.map +1 -0
- package/dist/testing/contracts/test-spec.types.d.ts +21 -0
- package/dist/testing/contracts/test-spec.types.d.ts.map +1 -0
- package/dist/testing/contracts/test-spec.types.js +3 -0
- package/dist/testing/contracts/test-spec.types.js.map +1 -0
- package/dist/testing/contracts/testing-metadata.types.d.ts +41 -0
- package/dist/testing/contracts/testing-metadata.types.d.ts.map +1 -0
- package/dist/testing/contracts/testing-metadata.types.js +3 -0
- package/dist/testing/contracts/testing-metadata.types.js.map +1 -0
- package/dist/testing/core/interpolation.d.ts +4 -0
- package/dist/testing/core/interpolation.d.ts.map +1 -0
- package/dist/testing/core/interpolation.js +180 -0
- package/dist/testing/core/interpolation.js.map +1 -0
- package/dist/testing/core/normalize-steps.d.ts +7 -0
- package/dist/testing/core/normalize-steps.d.ts.map +1 -0
- package/dist/testing/core/normalize-steps.js +20 -0
- package/dist/testing/core/normalize-steps.js.map +1 -0
- package/dist/testing/core/resource-store.d.ts +8 -0
- package/dist/testing/core/resource-store.d.ts.map +1 -0
- package/dist/testing/core/resource-store.js +41 -0
- package/dist/testing/core/resource-store.js.map +1 -0
- package/dist/testing/core/spec-registry.d.ts +10 -0
- package/dist/testing/core/spec-registry.d.ts.map +1 -0
- package/dist/testing/core/spec-registry.js +32 -0
- package/dist/testing/core/spec-registry.js.map +1 -0
- package/dist/testing/core/step-registry.d.ts +10 -0
- package/dist/testing/core/step-registry.d.ts.map +1 -0
- package/dist/testing/core/step-registry.js +26 -0
- package/dist/testing/core/step-registry.js.map +1 -0
- package/dist/testing/core/testing-engine.d.ts +14 -0
- package/dist/testing/core/testing-engine.d.ts.map +1 -0
- package/dist/testing/core/testing-engine.js +97 -0
- package/dist/testing/core/testing-engine.js.map +1 -0
- package/dist/testing/core/timeout.d.ts +2 -0
- package/dist/testing/core/timeout.d.ts.map +1 -0
- package/dist/testing/core/timeout.js +18 -0
- package/dist/testing/core/timeout.js.map +1 -0
- package/dist/testing/reporter/attachments.d.ts +4 -0
- package/dist/testing/reporter/attachments.d.ts.map +1 -0
- package/dist/testing/reporter/attachments.js +25 -0
- package/dist/testing/reporter/attachments.js.map +1 -0
- package/dist/testing/reporter/console-reporter.d.ts +45 -0
- package/dist/testing/reporter/console-reporter.d.ts.map +1 -0
- package/dist/testing/reporter/console-reporter.js +189 -0
- package/dist/testing/reporter/console-reporter.js.map +1 -0
- package/dist/testing/reporter/reporter.types.d.ts +37 -0
- package/dist/testing/reporter/reporter.types.d.ts.map +1 -0
- package/dist/testing/reporter/reporter.types.js +3 -0
- package/dist/testing/reporter/reporter.types.js.map +1 -0
- package/dist/testing/runner/lifecycle.d.ts +9 -0
- package/dist/testing/runner/lifecycle.d.ts.map +1 -0
- package/dist/testing/runner/lifecycle.js +33 -0
- package/dist/testing/runner/lifecycle.js.map +1 -0
- package/dist/testing/runner/run-from-metadata.d.ts +24 -0
- package/dist/testing/runner/run-from-metadata.d.ts.map +1 -0
- package/dist/testing/runner/run-from-metadata.js +70 -0
- package/dist/testing/runner/run-from-metadata.js.map +1 -0
- package/dist/testing/runner/scenario-filter.d.ts +9 -0
- package/dist/testing/runner/scenario-filter.d.ts.map +1 -0
- package/dist/testing/runner/scenario-filter.js +22 -0
- package/dist/testing/runner/scenario-filter.js.map +1 -0
- package/dist/testing/steps/api/auth.step.d.ts +3 -0
- package/dist/testing/steps/api/auth.step.d.ts.map +1 -0
- package/dist/testing/steps/api/auth.step.js +38 -0
- package/dist/testing/steps/api/auth.step.js.map +1 -0
- package/dist/testing/steps/api/index.d.ts +3 -0
- package/dist/testing/steps/api/index.d.ts.map +1 -0
- package/dist/testing/steps/api/index.js +10 -0
- package/dist/testing/steps/api/index.js.map +1 -0
- package/dist/testing/steps/api/request.step.d.ts +3 -0
- package/dist/testing/steps/api/request.step.d.ts.map +1 -0
- package/dist/testing/steps/api/request.step.js +281 -0
- package/dist/testing/steps/api/request.step.js.map +1 -0
- package/dist/testing/steps/assert/http.step.d.ts +3 -0
- package/dist/testing/steps/assert/http.step.d.ts.map +1 -0
- package/dist/testing/steps/assert/http.step.js +27 -0
- package/dist/testing/steps/assert/http.step.js.map +1 -0
- package/dist/testing/steps/assert/index.d.ts +3 -0
- package/dist/testing/steps/assert/index.d.ts.map +1 -0
- package/dist/testing/steps/assert/index.js +12 -0
- package/dist/testing/steps/assert/index.js.map +1 -0
- package/dist/testing/steps/assert/jsonpath.step.d.ts +3 -0
- package/dist/testing/steps/assert/jsonpath.step.d.ts.map +1 -0
- package/dist/testing/steps/assert/jsonpath.step.js +40 -0
- package/dist/testing/steps/assert/jsonpath.step.js.map +1 -0
- package/dist/testing/steps/assert/primitives.step.d.ts +3 -0
- package/dist/testing/steps/assert/primitives.step.d.ts.map +1 -0
- package/dist/testing/steps/assert/primitives.step.js +43 -0
- package/dist/testing/steps/assert/primitives.step.js.map +1 -0
- package/dist/testing/steps/test/index.d.ts +3 -0
- package/dist/testing/steps/test/index.d.ts.map +1 -0
- package/dist/testing/steps/test/index.js +8 -0
- package/dist/testing/steps/test/index.js.map +1 -0
- package/dist/testing/steps/test/test-spec.step.d.ts +3 -0
- package/dist/testing/steps/test/test-spec.step.d.ts.map +1 -0
- package/dist/testing/steps/test/test-spec.step.js +41 -0
- package/dist/testing/steps/test/test-spec.step.js.map +1 -0
- package/dist/testing/steps/ui/actions.step.d.ts +3 -0
- package/dist/testing/steps/ui/actions.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/actions.step.js +31 -0
- package/dist/testing/steps/ui/actions.step.js.map +1 -0
- package/dist/testing/steps/ui/assertions.step.d.ts +3 -0
- package/dist/testing/steps/ui/assertions.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/assertions.step.js +41 -0
- package/dist/testing/steps/ui/assertions.step.js.map +1 -0
- package/dist/testing/steps/ui/form.step.d.ts +3 -0
- package/dist/testing/steps/ui/form.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/form.step.js +34 -0
- package/dist/testing/steps/ui/form.step.js.map +1 -0
- package/dist/testing/steps/ui/index.d.ts +3 -0
- package/dist/testing/steps/ui/index.d.ts.map +1 -0
- package/dist/testing/steps/ui/index.js +14 -0
- package/dist/testing/steps/ui/index.js.map +1 -0
- package/dist/testing/steps/ui/navigation.step.d.ts +3 -0
- package/dist/testing/steps/ui/navigation.step.d.ts.map +1 -0
- package/dist/testing/steps/ui/navigation.step.js +39 -0
- package/dist/testing/steps/ui/navigation.step.js.map +1 -0
- package/dist/testing/steps/util/index.d.ts +3 -0
- package/dist/testing/steps/util/index.d.ts.map +1 -0
- package/dist/testing/steps/util/index.js +12 -0
- package/dist/testing/steps/util/index.js.map +1 -0
- package/dist/testing/steps/util/log.step.d.ts +3 -0
- package/dist/testing/steps/util/log.step.d.ts.map +1 -0
- package/dist/testing/steps/util/log.step.js +18 -0
- package/dist/testing/steps/util/log.step.js.map +1 -0
- package/dist/testing/steps/util/require.step.d.ts +3 -0
- package/dist/testing/steps/util/require.step.d.ts.map +1 -0
- package/dist/testing/steps/util/require.step.js +16 -0
- package/dist/testing/steps/util/require.step.js.map +1 -0
- package/dist/testing/steps/util/sleep.step.d.ts +3 -0
- package/dist/testing/steps/util/sleep.step.d.ts.map +1 -0
- package/dist/testing/steps/util/sleep.step.js +13 -0
- package/dist/testing/steps/util/sleep.step.js.map +1 -0
- package/docs/test-data-workflow.md +51 -11
- package/package.json +4 -2
- package/src/commands/run-tests.command.ts +278 -0
- package/src/commands/test-data.command.ts +26 -26
- package/src/commands/test.command.ts +14 -0
- package/src/controllers/service.controller.ts +58 -59
- package/src/dtos/basic-filters.dto.ts +0 -2
- package/src/dtos/create-user.dto.ts +1 -0
- package/src/index.ts +3 -0
- package/src/seeders/module-metadata-seeder.service.ts +3 -24
- package/src/seeders/module-test-data.service.ts +5 -3
- package/src/services/chatter-message.service.ts +18 -1
- package/src/services/crud.service.ts +1 -0
- package/src/services/queues/common.ts +75 -0
- package/src/services/queues/database-publisher.service.ts +4 -1
- package/src/services/queues/database-subscriber.service.ts +5 -3
- package/src/services/queues/rabbitmq-publisher.service.ts +17 -7
- package/src/services/queues/rabbitmq-subscriber.service.ts +9 -5
- package/src/solid-core.module.ts +4 -0
- package/src/testing/README.md +364 -0
- package/src/testing/__examples__/register-example-specs.ts +6 -0
- package/src/testing/__examples__/specs/custom-health.spec.ts +29 -0
- package/src/testing/__examples__/testing.sample.json +82 -0
- package/src/testing/adapters/api/api-adapter.ts +85 -0
- package/src/testing/adapters/api/api.types.ts +15 -0
- package/src/testing/adapters/ui/playwright-adapter.ts +54 -0
- package/src/testing/adapters/ui/ui.types.ts +4 -0
- package/src/testing/contracts/runtime-context.types.ts +36 -0
- package/src/testing/contracts/test-spec.types.ts +24 -0
- package/src/testing/contracts/testing-metadata.types.ts +46 -0
- package/src/testing/core/interpolation.ts +189 -0
- package/src/testing/core/normalize-steps.ts +21 -0
- package/src/testing/core/resource-store.ts +38 -0
- package/src/testing/core/spec-registry.ts +33 -0
- package/src/testing/core/step-registry.ts +27 -0
- package/src/testing/core/testing-engine.ts +127 -0
- package/src/testing/core/timeout.ts +19 -0
- package/src/testing/reporter/attachments.ts +25 -0
- package/src/testing/reporter/console-reporter.ts +229 -0
- package/src/testing/reporter/reporter.types.ts +36 -0
- package/src/testing/runner/lifecycle.ts +31 -0
- package/src/testing/runner/run-from-metadata.ts +87 -0
- package/src/testing/runner/scenario-filter.ts +33 -0
- package/src/testing/steps/api/auth.step.ts +66 -0
- package/src/testing/steps/api/index.ts +10 -0
- package/src/testing/steps/api/request.step.ts +358 -0
- package/src/testing/steps/assert/http.step.ts +33 -0
- package/src/testing/steps/assert/index.ts +12 -0
- package/src/testing/steps/assert/jsonpath.step.ts +50 -0
- package/src/testing/steps/assert/primitives.step.ts +69 -0
- package/src/testing/steps/test/index.ts +8 -0
- package/src/testing/steps/test/test-spec.step.ts +52 -0
- package/src/testing/steps/ui/actions.step.ts +36 -0
- package/src/testing/steps/ui/assertions.step.ts +54 -0
- package/src/testing/steps/ui/form.step.ts +39 -0
- package/src/testing/steps/ui/index.ts +12 -0
- package/src/testing/steps/ui/navigation.step.ts +53 -0
- package/src/testing/steps/util/index.ts +10 -0
- package/src/testing/steps/util/log.step.ts +19 -0
- package/src/testing/steps/util/require.step.ts +16 -0
- package/src/testing/steps/util/sleep.step.ts +15 -0
- package/tsconfig.json +35 -25
- package/tsconfig.tests.json +14 -0
- package/dist/tsconfig.tsbuildinfo +0 -1
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runFromMetadata = runFromMetadata;
|
|
4
|
+
const api_adapter_1 = require("../adapters/api/api-adapter");
|
|
5
|
+
const playwright_adapter_1 = require("../adapters/ui/playwright-adapter");
|
|
6
|
+
const api_1 = require("../steps/api");
|
|
7
|
+
const ui_1 = require("../steps/ui");
|
|
8
|
+
const assert_1 = require("../steps/assert");
|
|
9
|
+
const util_1 = require("../steps/util");
|
|
10
|
+
const test_1 = require("../steps/test");
|
|
11
|
+
const resource_store_1 = require("../core/resource-store");
|
|
12
|
+
const step_registry_1 = require("../core/step-registry");
|
|
13
|
+
const spec_registry_1 = require("../core/spec-registry");
|
|
14
|
+
const testing_engine_1 = require("../core/testing-engine");
|
|
15
|
+
const scenario_filter_1 = require("./scenario-filter");
|
|
16
|
+
const lifecycle_1 = require("./lifecycle");
|
|
17
|
+
const console_reporter_1 = require("../reporter/console-reporter");
|
|
18
|
+
function buildTestDataIndex(data) {
|
|
19
|
+
const index = {};
|
|
20
|
+
if (!Array.isArray(data))
|
|
21
|
+
return index;
|
|
22
|
+
for (const record of data) {
|
|
23
|
+
if (!record?.modelUserKey || !record?.recUserKeyValue)
|
|
24
|
+
continue;
|
|
25
|
+
if (!index[record.modelUserKey]) {
|
|
26
|
+
index[record.modelUserKey] = {};
|
|
27
|
+
}
|
|
28
|
+
index[record.modelUserKey][record.recUserKeyValue] = record.data ?? {};
|
|
29
|
+
}
|
|
30
|
+
return index;
|
|
31
|
+
}
|
|
32
|
+
async function runFromMetadata(opts) {
|
|
33
|
+
const registry = new step_registry_1.StepRegistry();
|
|
34
|
+
(0, api_1.registerApiSteps)(registry);
|
|
35
|
+
(0, ui_1.registerUiSteps)(registry);
|
|
36
|
+
(0, assert_1.registerAssertSteps)(registry);
|
|
37
|
+
(0, util_1.registerUtilSteps)(registry);
|
|
38
|
+
(0, test_1.registerTestSteps)(registry);
|
|
39
|
+
const engine = new testing_engine_1.TestingEngine(registry, opts.defaults);
|
|
40
|
+
const scenarios = (0, scenario_filter_1.filterScenarios)(opts.metadata.testing.scenarios, {
|
|
41
|
+
scenarioIds: opts.scenarioIds,
|
|
42
|
+
includeTags: opts.includeTags,
|
|
43
|
+
skipScenarioIds: opts.skipScenarioIds,
|
|
44
|
+
});
|
|
45
|
+
const specRegistry = new spec_registry_1.SpecRegistry();
|
|
46
|
+
const testData = buildTestDataIndex(opts.metadata.testing?.data);
|
|
47
|
+
if (opts.specs) {
|
|
48
|
+
opts.specs(specRegistry);
|
|
49
|
+
}
|
|
50
|
+
const resources = new resource_store_1.SimpleResourceStore();
|
|
51
|
+
const reporter = opts.reporter ?? new console_reporter_1.ConsoleReporter();
|
|
52
|
+
const api = new api_adapter_1.ApiAdapter(opts.api);
|
|
53
|
+
const ui = new playwright_adapter_1.PlaywrightAdapter(opts.ui);
|
|
54
|
+
const ctxBase = { resources, reporter, api, ui, specRegistry, testData, options: opts.options };
|
|
55
|
+
const uiStarted = { value: false };
|
|
56
|
+
try {
|
|
57
|
+
for (const scenario of scenarios) {
|
|
58
|
+
if ((0, lifecycle_1.scenarioNeedsUi)(scenario)) {
|
|
59
|
+
await (0, lifecycle_1.ensureUiStarted)(ctxBase, uiStarted);
|
|
60
|
+
}
|
|
61
|
+
await engine.runScenario(scenario, ctxBase);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
finally {
|
|
65
|
+
if (uiStarted.value) {
|
|
66
|
+
await ui.stop();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=run-from-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-from-metadata.js","sourceRoot":"","sources":["../../../src/testing/runner/run-from-metadata.ts"],"names":[],"mappings":";;AA8CA,0CAwCC;AAlFD,6DAAyD;AACzD,0EAAsE;AACtE,sCAAgD;AAChD,oCAA8C;AAC9C,4CAAsD;AACtD,wCAAkD;AAClD,wCAAkD;AAClD,2DAA6D;AAC7D,yDAAqD;AACrD,yDAAqD;AACrD,2DAAuD;AACvD,uDAAoD;AACpD,2CAA+D;AAC/D,mEAA+D;AAG/D,SAAS,kBAAkB,CAAC,IAA0B;IACpD,MAAM,KAAK,GAAwC,EAAE,CAAC;IACtD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IACvC,KAAK,MAAM,MAAM,IAAI,IAAI,EAAE,CAAC;QAC1B,IAAI,CAAC,MAAM,EAAE,YAAY,IAAI,CAAC,MAAM,EAAE,eAAe;YAAE,SAAS;QAChE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,EAAE,CAAC;YAChC,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC;QAClC,CAAC;QACD,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;IACzE,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAeM,KAAK,UAAU,eAAe,CAAC,IAAmB;IACvD,MAAM,QAAQ,GAAG,IAAI,4BAAY,EAAE,CAAC;IACpC,IAAA,sBAAgB,EAAC,QAAQ,CAAC,CAAC;IAC3B,IAAA,oBAAe,EAAC,QAAQ,CAAC,CAAC;IAC1B,IAAA,4BAAmB,EAAC,QAAQ,CAAC,CAAC;IAC9B,IAAA,wBAAiB,EAAC,QAAQ,CAAC,CAAC;IAC5B,IAAA,wBAAiB,EAAC,QAAQ,CAAC,CAAC;IAE5B,MAAM,MAAM,GAAG,IAAI,8BAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IAC1D,MAAM,SAAS,GAAG,IAAA,iCAAe,EAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,EAAE;QACjE,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,eAAe,EAAE,IAAI,CAAC,eAAe;KACtC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,IAAI,4BAAY,EAAE,CAAC;IACxC,MAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACjE,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3B,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,oCAAmB,EAAE,CAAC;IAC5C,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,IAAI,kCAAe,EAAE,CAAC;IACxD,MAAM,GAAG,GAAG,IAAI,wBAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,EAAE,GAAG,IAAI,sCAAiB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC1C,MAAM,OAAO,GAAG,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,YAAY,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC;IAChG,MAAM,SAAS,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IAEnC,IAAI,CAAC;QACH,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;YACjC,IAAI,IAAA,2BAAe,EAAC,QAAQ,CAAC,EAAE,CAAC;gBAC9B,MAAM,IAAA,2BAAe,EAAC,OAAO,EAAE,SAAS,CAAC,CAAC;YAC5C,CAAC;YACD,MAAM,MAAM,CAAC,WAAW,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,SAAS,CAAC,KAAK,EAAE,CAAC;YACpB,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC;QAClB,CAAC;IACH,CAAC;AACH,CAAC","sourcesContent":["import type { Reporter } from \"../reporter/reporter.types\";\nimport type { TestingMetadata, TestingDataRecord } from \"../contracts/testing-metadata.types\";\nimport type { ApiAdapterOptions } from \"../adapters/api/api.types\";\nimport type { PlaywrightAdapterOptions } from \"../adapters/ui/ui.types\";\nimport { ApiAdapter } from \"../adapters/api/api-adapter\";\nimport { PlaywrightAdapter } from \"../adapters/ui/playwright-adapter\";\nimport { registerApiSteps } from \"../steps/api\";\nimport { registerUiSteps } from \"../steps/ui\";\nimport { registerAssertSteps } from \"../steps/assert\";\nimport { registerUtilSteps } from \"../steps/util\";\nimport { registerTestSteps } from \"../steps/test\";\nimport { SimpleResourceStore } from \"../core/resource-store\";\nimport { StepRegistry } from \"../core/step-registry\";\nimport { SpecRegistry } from \"../core/spec-registry\";\nimport { TestingEngine } from \"../core/testing-engine\";\nimport { filterScenarios } from \"./scenario-filter\";\nimport { ensureUiStarted, scenarioNeedsUi } from \"./lifecycle\";\nimport { ConsoleReporter } from \"../reporter/console-reporter\";\n\n\nfunction buildTestDataIndex(data?: TestingDataRecord[]): Record<string, Record<string, any>> {\n const index: Record<string, Record<string, any>> = {};\n if (!Array.isArray(data)) return index;\n for (const record of data) {\n if (!record?.modelUserKey || !record?.recUserKeyValue) continue;\n if (!index[record.modelUserKey]) {\n index[record.modelUserKey] = {};\n }\n index[record.modelUserKey][record.recUserKeyValue] = record.data ?? {};\n }\n return index;\n}\n\nexport type RunnerOptions = {\n metadata: TestingMetadata;\n scenarioIds?: string[];\n includeTags?: string[];\n skipScenarioIds?: string[];\n reporter?: Reporter;\n api?: ApiAdapterOptions;\n ui?: PlaywrightAdapterOptions;\n defaults?: { timeoutMs?: number; retries?: number };\n options?: { printApiLogs?: boolean };\n specs?: (registry: SpecRegistry) => void;\n};\n\nexport async function runFromMetadata(opts: RunnerOptions): Promise<void> {\n const registry = new StepRegistry();\n registerApiSteps(registry);\n registerUiSteps(registry);\n registerAssertSteps(registry);\n registerUtilSteps(registry);\n registerTestSteps(registry);\n\n const engine = new TestingEngine(registry, opts.defaults);\n const scenarios = filterScenarios(opts.metadata.testing.scenarios, {\n scenarioIds: opts.scenarioIds,\n includeTags: opts.includeTags,\n skipScenarioIds: opts.skipScenarioIds,\n });\n\n const specRegistry = new SpecRegistry();\n const testData = buildTestDataIndex(opts.metadata.testing?.data);\n if (opts.specs) {\n opts.specs(specRegistry);\n }\n\n const resources = new SimpleResourceStore();\n const reporter = opts.reporter ?? new ConsoleReporter();\n const api = new ApiAdapter(opts.api);\n const ui = new PlaywrightAdapter(opts.ui);\n const ctxBase = { resources, reporter, api, ui, specRegistry, testData, options: opts.options };\n const uiStarted = { value: false };\n\n try {\n for (const scenario of scenarios) {\n if (scenarioNeedsUi(scenario)) {\n await ensureUiStarted(ctxBase, uiStarted);\n }\n await engine.runScenario(scenario, ctxBase);\n }\n } finally {\n if (uiStarted.value) {\n await ui.stop();\n }\n }\n}\n"]}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ScenarioSpec } from "../contracts/testing-metadata.types";
|
|
2
|
+
type ScenarioFilterOptions = {
|
|
3
|
+
scenarioIds?: string[];
|
|
4
|
+
includeTags?: string[];
|
|
5
|
+
skipScenarioIds?: string[];
|
|
6
|
+
};
|
|
7
|
+
export declare function filterScenarios(scenarios: ScenarioSpec[], opts: ScenarioFilterOptions): ScenarioSpec[];
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=scenario-filter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-filter.d.ts","sourceRoot":"","sources":["../../../src/testing/runner/scenario-filter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAExE,KAAK,qBAAqB,GAAG;IAC3B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B,CAAC;AAEF,wBAAgB,eAAe,CAC7B,SAAS,EAAE,YAAY,EAAE,EACzB,IAAI,EAAE,qBAAqB,GAC1B,YAAY,EAAE,CAqBhB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.filterScenarios = filterScenarios;
|
|
4
|
+
function filterScenarios(scenarios, opts) {
|
|
5
|
+
let result = scenarios;
|
|
6
|
+
if (opts.scenarioIds && opts.scenarioIds.length > 0) {
|
|
7
|
+
const idSet = new Set(opts.scenarioIds);
|
|
8
|
+
result = result.filter((scenario) => idSet.has(scenario.id));
|
|
9
|
+
}
|
|
10
|
+
if (opts.includeTags && opts.includeTags.length > 0) {
|
|
11
|
+
result = result.filter((scenario) => {
|
|
12
|
+
const tags = scenario.tags ?? [];
|
|
13
|
+
return opts.includeTags.every((tag) => tags.includes(tag));
|
|
14
|
+
});
|
|
15
|
+
}
|
|
16
|
+
if (opts.skipScenarioIds && opts.skipScenarioIds.length > 0) {
|
|
17
|
+
const skipSet = new Set(opts.skipScenarioIds);
|
|
18
|
+
result = result.filter((scenario) => !skipSet.has(scenario.id));
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
22
|
+
//# sourceMappingURL=scenario-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scenario-filter.js","sourceRoot":"","sources":["../../../src/testing/runner/scenario-filter.ts"],"names":[],"mappings":";;AAQA,0CAwBC;AAxBD,SAAgB,eAAe,CAC7B,SAAyB,EACzB,IAA2B;IAE3B,IAAI,MAAM,GAAG,SAAS,CAAC;IAEvB,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QACxC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAC/D,CAAC;IAED,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE;YAClC,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,IAAI,EAAE,CAAC;YACjC,OAAO,IAAI,CAAC,WAAY,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5D,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC;QAC9C,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;IAClE,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC","sourcesContent":["import type { ScenarioSpec } from \"../contracts/testing-metadata.types\";\n\ntype ScenarioFilterOptions = {\n scenarioIds?: string[];\n includeTags?: string[];\n skipScenarioIds?: string[];\n};\n\nexport function filterScenarios(\n scenarios: ScenarioSpec[],\n opts: ScenarioFilterOptions,\n): ScenarioSpec[] {\n let result = scenarios;\n\n if (opts.scenarioIds && opts.scenarioIds.length > 0) {\n const idSet = new Set(opts.scenarioIds);\n result = result.filter((scenario) => idSet.has(scenario.id));\n }\n\n if (opts.includeTags && opts.includeTags.length > 0) {\n result = result.filter((scenario) => {\n const tags = scenario.tags ?? [];\n return opts.includeTags!.every((tag) => tags.includes(tag));\n });\n }\n\n if (opts.skipScenarioIds && opts.skipScenarioIds.length > 0) {\n const skipSet = new Set(opts.skipScenarioIds);\n result = result.filter((scenario) => !skipSet.has(scenario.id));\n }\n\n return result;\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.step.d.ts","sourceRoot":"","sources":["../../../../src/testing/steps/api/auth.step.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAQxD,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAqD9D"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAuthSteps = registerAuthSteps;
|
|
4
|
+
function registerAuthSteps(registry) {
|
|
5
|
+
registry.register("api.auth.bearerFromLogin", async (ctx, step) => {
|
|
6
|
+
if (!ctx.api) {
|
|
7
|
+
throw new Error('Missing API adapter on context for op "api.auth.bearerFromLogin"');
|
|
8
|
+
}
|
|
9
|
+
const input = (step.with ?? {});
|
|
10
|
+
if (!input.url) {
|
|
11
|
+
throw new Error('Missing "url" in step.with for op "api.auth.bearerFromLogin"');
|
|
12
|
+
}
|
|
13
|
+
if (!input.username) {
|
|
14
|
+
throw new Error('Missing "username" in step.with for op "api.auth.bearerFromLogin"');
|
|
15
|
+
}
|
|
16
|
+
if (!input.password) {
|
|
17
|
+
throw new Error('Missing "password" in step.with for op "api.auth.bearerFromLogin"');
|
|
18
|
+
}
|
|
19
|
+
const response = await ctx.api.http({
|
|
20
|
+
method: "POST",
|
|
21
|
+
url: input.url,
|
|
22
|
+
json: {
|
|
23
|
+
username: input.username,
|
|
24
|
+
password: input.password,
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
if (response.status < 200 || response.status >= 300) {
|
|
28
|
+
throw new Error(`Login failed with status ${response.status}: ${response.bodyText}`);
|
|
29
|
+
}
|
|
30
|
+
const bodyJson = response.bodyJson;
|
|
31
|
+
const token = bodyJson?.data?.accessToken ?? bodyJson?.accessToken;
|
|
32
|
+
if (typeof token !== "string" || token.length === 0) {
|
|
33
|
+
throw new Error("Missing access token in login response");
|
|
34
|
+
}
|
|
35
|
+
return token;
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=auth.step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.step.js","sourceRoot":"","sources":["../../../../src/testing/steps/api/auth.step.ts"],"names":[],"mappings":";;AAYA,8CAqDC;AArDD,SAAgB,iBAAiB,CAAC,QAAsB;IACtD,QAAQ,CAAC,QAAQ,CACf,0BAA0B,EAC1B,KAAK,EAAE,GAAgB,EAAE,IAAY,EAAE,EAAE;QACvC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CACb,kEAAkE,CACnE,CAAC;QACJ,CAAC;QAED,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAyB,CAAC;QACxD,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC;YACf,MAAM,IAAI,KAAK,CACb,8DAA8D,CAC/D,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACb,mEAAmE,CACpE,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC;YAClC,MAAM,EAAE,MAAM;YACd,GAAG,EAAE,KAAK,CAAC,GAAG;YACd,IAAI,EAAE;gBACJ,QAAQ,EAAE,KAAK,CAAC,QAAQ;gBACxB,QAAQ,EAAE,KAAK,CAAC,QAAQ;aACzB;SACF,CAAC,CAAC;QAEH,IAAI,QAAQ,CAAC,MAAM,GAAG,GAAG,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CACb,4BAA4B,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,QAAQ,EAAE,CACpE,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAEb,CAAC;QACd,MAAM,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,WAAW,IAAI,QAAQ,EAAE,WAAW,CAAC;QACnE,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpD,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;QAC5D,CAAC;QAED,OAAO,KAAK,CAAC;IACf,CAAC,CACF,CAAC;AACJ,CAAC","sourcesContent":["// Purpose: API auth step registrations.\n\nimport type { TestContext } from \"../../contracts/runtime-context.types\";\nimport type { OpStep } from \"../../contracts/testing-metadata.types\";\nimport { StepRegistry } from \"../../core/step-registry\";\n\ntype BearerFromLoginInput = {\n url: string;\n username: string;\n password: string;\n};\n\nexport function registerAuthSteps(registry: StepRegistry): void {\n registry.register(\n \"api.auth.bearerFromLogin\",\n async (ctx: TestContext, step: OpStep) => {\n if (!ctx.api) {\n throw new Error(\n 'Missing API adapter on context for op \"api.auth.bearerFromLogin\"',\n );\n }\n\n const input = (step.with ?? {}) as BearerFromLoginInput;\n if (!input.url) {\n throw new Error(\n 'Missing \"url\" in step.with for op \"api.auth.bearerFromLogin\"',\n );\n }\n if (!input.username) {\n throw new Error(\n 'Missing \"username\" in step.with for op \"api.auth.bearerFromLogin\"',\n );\n }\n if (!input.password) {\n throw new Error(\n 'Missing \"password\" in step.with for op \"api.auth.bearerFromLogin\"',\n );\n }\n\n const response = await ctx.api.http({\n method: \"POST\",\n url: input.url,\n json: {\n username: input.username,\n password: input.password,\n },\n });\n\n if (response.status < 200 || response.status >= 300) {\n throw new Error(\n `Login failed with status ${response.status}: ${response.bodyText}`,\n );\n }\n\n const bodyJson = response.bodyJson as\n | { data?: { accessToken?: unknown }; accessToken?: unknown }\n | undefined;\n const token = bodyJson?.data?.accessToken ?? bodyJson?.accessToken;\n if (typeof token !== \"string\" || token.length === 0) {\n throw new Error(\"Missing access token in login response\");\n }\n\n return token;\n },\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/steps/api/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIxD,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAG7D"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerApiSteps = registerApiSteps;
|
|
4
|
+
const auth_step_1 = require("./auth.step");
|
|
5
|
+
const request_step_1 = require("./request.step");
|
|
6
|
+
function registerApiSteps(registry) {
|
|
7
|
+
(0, request_step_1.registerApiRequestStep)(registry);
|
|
8
|
+
(0, auth_step_1.registerAuthSteps)(registry);
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/testing/steps/api/index.ts"],"names":[],"mappings":";;AAMA,4CAGC;AAND,2CAAgD;AAChD,iDAAwD;AAExD,SAAgB,gBAAgB,CAAC,QAAsB;IACrD,IAAA,qCAAsB,EAAC,QAAQ,CAAC,CAAC;IACjC,IAAA,6BAAiB,EAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC","sourcesContent":["// Purpose: API step registrations.\n\nimport { StepRegistry } from \"../../core/step-registry\";\nimport { registerAuthSteps } from \"./auth.step\";\nimport { registerApiRequestStep } from \"./request.step\";\n\nexport function registerApiSteps(registry: StepRegistry): void {\n registerApiRequestStep(registry);\n registerAuthSteps(registry);\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.step.d.ts","sourceRoot":"","sources":["../../../../src/testing/steps/api/request.step.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AA8NxD,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAoInE"}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerApiRequestStep = registerApiRequestStep;
|
|
7
|
+
const attachments_1 = require("../../reporter/attachments");
|
|
8
|
+
const form_data_1 = __importDefault(require("form-data"));
|
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
+
const fs_1 = __importDefault(require("fs"));
|
|
11
|
+
const https_1 = __importDefault(require("https"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
const qs_1 = __importDefault(require("qs"));
|
|
14
|
+
const TMP_DIR = "/tmp/.solidx-testing-files";
|
|
15
|
+
const MAX_REDIRECTS = 5;
|
|
16
|
+
function isPlainObject(value) {
|
|
17
|
+
return !!value && typeof value === "object" && !Array.isArray(value);
|
|
18
|
+
}
|
|
19
|
+
function deepMerge(target, source) {
|
|
20
|
+
if (isPlainObject(target) && isPlainObject(source)) {
|
|
21
|
+
const out = { ...target };
|
|
22
|
+
for (const [key, value] of Object.entries(source)) {
|
|
23
|
+
if (key in out) {
|
|
24
|
+
out[key] = deepMerge(out[key], value);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
out[key] = value;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
return source;
|
|
33
|
+
}
|
|
34
|
+
function stripUndefined(value) {
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
return value.map(stripUndefined);
|
|
37
|
+
}
|
|
38
|
+
if (isPlainObject(value)) {
|
|
39
|
+
const out = {};
|
|
40
|
+
for (const [key, val] of Object.entries(value)) {
|
|
41
|
+
if (val === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
out[key] = stripUndefined(val);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
function ensureTmpDir() {
|
|
50
|
+
fs_1.default.mkdirSync(TMP_DIR, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
function buildTempFilePath(urlObj) {
|
|
53
|
+
const ext = path_1.default.extname(urlObj.pathname);
|
|
54
|
+
const base = path_1.default.basename(urlObj.pathname, ext) || "file";
|
|
55
|
+
const safeBase = base.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
56
|
+
const suffix = `${Date.now()}-${crypto_1.default.randomBytes(6).toString("hex")}`;
|
|
57
|
+
return path_1.default.join(TMP_DIR, `${safeBase}-${suffix}${ext}`);
|
|
58
|
+
}
|
|
59
|
+
function downloadUrlToFile(urlValue, redirectCount = 0) {
|
|
60
|
+
if (!urlValue.startsWith("https://")) {
|
|
61
|
+
throw new Error(`Only https URLs are allowed for file downloads: ${urlValue}`);
|
|
62
|
+
}
|
|
63
|
+
if (redirectCount > MAX_REDIRECTS) {
|
|
64
|
+
throw new Error(`Too many redirects while downloading file: ${urlValue}`);
|
|
65
|
+
}
|
|
66
|
+
const urlObj = new URL(urlValue);
|
|
67
|
+
ensureTmpDir();
|
|
68
|
+
const filePath = buildTempFilePath(urlObj);
|
|
69
|
+
return new Promise((resolve, reject) => {
|
|
70
|
+
const fileStream = fs_1.default.createWriteStream(filePath);
|
|
71
|
+
const request = https_1.default.get(urlObj, (response) => {
|
|
72
|
+
const status = response.statusCode ?? 0;
|
|
73
|
+
const location = response.headers.location;
|
|
74
|
+
if (status >= 300 && status < 400 && location) {
|
|
75
|
+
response.resume();
|
|
76
|
+
fileStream.close(() => fs_1.default.unlink(filePath, () => { }));
|
|
77
|
+
const nextUrl = new URL(location, urlObj).toString();
|
|
78
|
+
downloadUrlToFile(nextUrl, redirectCount + 1).then(resolve).catch(reject);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (status >= 400) {
|
|
82
|
+
response.resume();
|
|
83
|
+
fileStream.close(() => fs_1.default.unlink(filePath, () => { }));
|
|
84
|
+
reject(new Error(`Failed to download file. HTTP ${status}`));
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
response.pipe(fileStream);
|
|
88
|
+
});
|
|
89
|
+
request.on("error", (err) => {
|
|
90
|
+
fileStream.close(() => fs_1.default.unlink(filePath, () => { }));
|
|
91
|
+
reject(err);
|
|
92
|
+
});
|
|
93
|
+
fileStream.on("error", (err) => {
|
|
94
|
+
request.destroy();
|
|
95
|
+
fs_1.default.unlink(filePath, () => { });
|
|
96
|
+
reject(err);
|
|
97
|
+
});
|
|
98
|
+
fileStream.on("finish", () => {
|
|
99
|
+
fileStream.close(() => resolve(filePath));
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
function resolveFilePath(rawValue) {
|
|
104
|
+
const value = rawValue.startsWith("file:") ? rawValue.slice(5) : rawValue;
|
|
105
|
+
if (!value) {
|
|
106
|
+
throw new Error('Invalid file reference. Use "file:/absolute/path".');
|
|
107
|
+
}
|
|
108
|
+
if (!path_1.default.isAbsolute(value)) {
|
|
109
|
+
throw new Error(`File path must be absolute: ${value}`);
|
|
110
|
+
}
|
|
111
|
+
if (!fs_1.default.existsSync(value)) {
|
|
112
|
+
throw new Error(`File does not exist: ${value}`);
|
|
113
|
+
}
|
|
114
|
+
return value;
|
|
115
|
+
}
|
|
116
|
+
async function resolveFileReference(rawValue) {
|
|
117
|
+
if (rawValue.startsWith("url:")) {
|
|
118
|
+
const urlValue = rawValue.slice(4);
|
|
119
|
+
if (!urlValue) {
|
|
120
|
+
throw new Error('Invalid url reference. Use "url:https://...".');
|
|
121
|
+
}
|
|
122
|
+
const filePath = await downloadUrlToFile(urlValue);
|
|
123
|
+
return { filePath, source: "url", url: urlValue };
|
|
124
|
+
}
|
|
125
|
+
return { filePath: resolveFilePath(rawValue), source: "file" };
|
|
126
|
+
}
|
|
127
|
+
function formItemsFromRecord(record) {
|
|
128
|
+
return Object.entries(record).map(([name, value]) => {
|
|
129
|
+
if (typeof value === "string" && (value.startsWith("file:") || value.startsWith("url:"))) {
|
|
130
|
+
return { name, value, type: "file" };
|
|
131
|
+
}
|
|
132
|
+
return { name, value };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async function buildFormData(items) {
|
|
136
|
+
const form = new form_data_1.default();
|
|
137
|
+
const logItems = [];
|
|
138
|
+
for (const item of items) {
|
|
139
|
+
if (!item?.name) {
|
|
140
|
+
throw new Error('Form item is missing required "name".');
|
|
141
|
+
}
|
|
142
|
+
const rawValue = item.value;
|
|
143
|
+
const isFile = item.type === "file" || (typeof rawValue === "string" && (rawValue.startsWith("file:") || rawValue.startsWith("url:")));
|
|
144
|
+
if (isFile) {
|
|
145
|
+
if (typeof rawValue !== "string") {
|
|
146
|
+
throw new Error(`Form file value must be a string for field: ${item.name}`);
|
|
147
|
+
}
|
|
148
|
+
const resolved = await resolveFileReference(rawValue);
|
|
149
|
+
const filename = item.filename ?? path_1.default.basename(resolved.filePath);
|
|
150
|
+
form.append(item.name, fs_1.default.createReadStream(resolved.filePath), {
|
|
151
|
+
filename,
|
|
152
|
+
contentType: item.contentType,
|
|
153
|
+
});
|
|
154
|
+
logItems.push({
|
|
155
|
+
name: item.name,
|
|
156
|
+
type: "file",
|
|
157
|
+
source: resolved.source,
|
|
158
|
+
url: resolved.url,
|
|
159
|
+
path: resolved.filePath,
|
|
160
|
+
filename,
|
|
161
|
+
contentType: item.contentType,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const textValue = rawValue === undefined || rawValue === null ? "" : typeof rawValue === "string" ? rawValue : JSON.stringify(rawValue);
|
|
166
|
+
form.append(item.name, textValue);
|
|
167
|
+
logItems.push({
|
|
168
|
+
name: item.name,
|
|
169
|
+
type: "text",
|
|
170
|
+
value: textValue,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return { form, logItems };
|
|
175
|
+
}
|
|
176
|
+
function registerApiRequestStep(registry) {
|
|
177
|
+
registry.register("api.request", async (ctx, step) => {
|
|
178
|
+
if (!ctx.api) {
|
|
179
|
+
throw new Error('Missing API adapter on context for op "api.request"');
|
|
180
|
+
}
|
|
181
|
+
const input = (step.with ?? {});
|
|
182
|
+
if (!input.method) {
|
|
183
|
+
throw new Error('Missing "method" in step.with for op "api.request"');
|
|
184
|
+
}
|
|
185
|
+
if (!input.url) {
|
|
186
|
+
throw new Error('Missing "url" in step.with for op "api.request"');
|
|
187
|
+
}
|
|
188
|
+
const rawFormData = input.formData ?? input.body;
|
|
189
|
+
const formItems = Array.isArray(rawFormData) ? rawFormData : isPlainObject(rawFormData) ? formItemsFromRecord(rawFormData) : undefined;
|
|
190
|
+
if (rawFormData !== undefined && !formItems) {
|
|
191
|
+
throw new Error('formData/body must be an array of items or an object, for op "api.request".');
|
|
192
|
+
}
|
|
193
|
+
if (formItems && (input.json !== undefined || input.bodyText !== undefined)) {
|
|
194
|
+
throw new Error('Use either formData/body or json/bodyText, not both, for op "api.request".');
|
|
195
|
+
}
|
|
196
|
+
const rawUrl = input.url;
|
|
197
|
+
const absolute = /^[a-z][a-z0-9+.-]*:/i.test(rawUrl);
|
|
198
|
+
const urlObj = new URL(rawUrl, "http://solid.local");
|
|
199
|
+
const urlQuery = urlObj.search
|
|
200
|
+
? qs_1.default.parse(urlObj.search, { ignoreQueryPrefix: true, allowDots: true, depth: 10 })
|
|
201
|
+
: {};
|
|
202
|
+
let stepQuery = {};
|
|
203
|
+
if (input.query !== undefined) {
|
|
204
|
+
stepQuery =
|
|
205
|
+
typeof input.query === "string"
|
|
206
|
+
? qs_1.default.parse(input.query, { ignoreQueryPrefix: true, allowDots: true, depth: 10 })
|
|
207
|
+
: input.query;
|
|
208
|
+
}
|
|
209
|
+
const mergedQuery = deepMerge(urlQuery, stepQuery);
|
|
210
|
+
const queryString = qs_1.default.stringify(mergedQuery, { addQueryPrefix: true, allowDots: true });
|
|
211
|
+
urlObj.search = queryString;
|
|
212
|
+
const finalUrl = absolute
|
|
213
|
+
? urlObj.toString()
|
|
214
|
+
: `${urlObj.pathname}${urlObj.search}${urlObj.hash}`;
|
|
215
|
+
let formData;
|
|
216
|
+
let formLogItems;
|
|
217
|
+
if (formItems) {
|
|
218
|
+
const built = await buildFormData(formItems);
|
|
219
|
+
formData = built.form;
|
|
220
|
+
formLogItems = built.logItems;
|
|
221
|
+
}
|
|
222
|
+
const req = {
|
|
223
|
+
method: input.method,
|
|
224
|
+
url: finalUrl,
|
|
225
|
+
headers: input.headers,
|
|
226
|
+
json: input.json,
|
|
227
|
+
bodyText: input.bodyText,
|
|
228
|
+
formData,
|
|
229
|
+
};
|
|
230
|
+
const startedAt = Date.now();
|
|
231
|
+
const printApiLogs = ctx.options?.printApiLogs ?? false;
|
|
232
|
+
const logName = `api.request ${req.method} ${finalUrl}`;
|
|
233
|
+
const requestLog = stripUndefined({
|
|
234
|
+
method: req.method,
|
|
235
|
+
url: finalUrl,
|
|
236
|
+
headers: req.headers,
|
|
237
|
+
query: mergedQuery,
|
|
238
|
+
queryString: urlObj.search ? urlObj.search.slice(1) : "",
|
|
239
|
+
json: input.json,
|
|
240
|
+
bodyText: input.bodyText,
|
|
241
|
+
formData: formLogItems,
|
|
242
|
+
});
|
|
243
|
+
let response;
|
|
244
|
+
try {
|
|
245
|
+
response = await ctx.api.http(req);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
if (printApiLogs) {
|
|
249
|
+
(0, attachments_1.attachJson)(ctx, logName, {
|
|
250
|
+
request: requestLog,
|
|
251
|
+
durationMs: Date.now() - startedAt,
|
|
252
|
+
error: err instanceof Error ? err.message : String(err),
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
throw err;
|
|
256
|
+
}
|
|
257
|
+
const durationMs = Date.now() - startedAt;
|
|
258
|
+
ctx.last = { ...(ctx.last ?? {}), apiResponse: response };
|
|
259
|
+
if (printApiLogs) {
|
|
260
|
+
const responseLog = stripUndefined({
|
|
261
|
+
status: response.status,
|
|
262
|
+
headers: response.headers,
|
|
263
|
+
bodyJson: response.bodyJson,
|
|
264
|
+
bodyText: response.bodyJson === undefined ? response.bodyText : undefined,
|
|
265
|
+
});
|
|
266
|
+
(0, attachments_1.attachJson)(ctx, logName, {
|
|
267
|
+
request: requestLog,
|
|
268
|
+
response: responseLog,
|
|
269
|
+
durationMs,
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
status: response.status,
|
|
274
|
+
headers: response.headers,
|
|
275
|
+
bodyText: response.bodyText,
|
|
276
|
+
bodyJson: response.bodyJson,
|
|
277
|
+
body: response.bodyJson ?? response.bodyText,
|
|
278
|
+
};
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
//# sourceMappingURL=request.step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"request.step.js","sourceRoot":"","sources":["../../../../src/testing/steps/api/request.step.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ApiRequestOptions } from \"../../adapters/api/api.types\";\nimport type { TestContext } from \"../../contracts/runtime-context.types\";\nimport type { OpStep } from \"../../contracts/testing-metadata.types\";\nimport { StepRegistry } from \"../../core/step-registry\";\nimport { attachJson } from \"../../reporter/attachments\";\nimport FormData from \"form-data\";\nimport crypto from \"crypto\";\nimport fs from \"fs\";\nimport https from \"https\";\nimport path from \"path\";\nimport qs from \"qs\";\n\nconst TMP_DIR = \"/tmp/.solidx-testing-files\";\nconst MAX_REDIRECTS = 5;\n\ntype ApiFormItem = {\n name: string;\n value: any;\n type?: \"text\" | \"file\";\n filename?: string;\n contentType?: string;\n};\n\ntype ApiRequestInput = {\n method: string;\n url: string;\n headers?: Record<string, string>;\n json?: unknown;\n bodyText?: string;\n // Extra query params (object or querystring).\n query?: Record<string, any> | string;\n // Form data payload (array of items).\n formData?: ApiFormItem[] | Record<string, any>;\n // Alias for formData (array or object).\n body?: ApiFormItem[] | Record<string, any>;\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, any> {\n // Only merge plain objects.\n return !!value && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction deepMerge(target: any, source: any): any {\n if (isPlainObject(target) && isPlainObject(source)) {\n // Clone target to avoid mutation.\n const out: Record<string, any> = { ...target };\n for (const [key, value] of Object.entries(source)) {\n if (key in out) {\n // Merge nested keys.\n out[key] = deepMerge(out[key], value);\n } else {\n // Add new keys.\n out[key] = value;\n }\n }\n // Return merged object.\n return out;\n }\n // For non-objects, source wins.\n return source;\n}\n\nfunction stripUndefined(value: any): any {\n if (Array.isArray(value)) {\n return value.map(stripUndefined);\n }\n if (isPlainObject(value)) {\n const out: Record<string, any> = {};\n for (const [key, val] of Object.entries(value)) {\n if (val === undefined) continue;\n out[key] = stripUndefined(val);\n }\n return out;\n }\n return value;\n}\n\nfunction ensureTmpDir(): void {\n fs.mkdirSync(TMP_DIR, { recursive: true });\n}\n\nfunction buildTempFilePath(urlObj: URL): string {\n const ext = path.extname(urlObj.pathname);\n const base = path.basename(urlObj.pathname, ext) || \"file\";\n const safeBase = base.replace(/[^a-zA-Z0-9-_]/g, \"_\");\n const suffix = `${Date.now()}-${crypto.randomBytes(6).toString(\"hex\")}`;\n return path.join(TMP_DIR, `${safeBase}-${suffix}${ext}`);\n}\n\nfunction downloadUrlToFile(urlValue: string, redirectCount: number = 0): Promise<string> {\n if (!urlValue.startsWith(\"https://\")) {\n throw new Error(`Only https URLs are allowed for file downloads: ${urlValue}`);\n }\n if (redirectCount > MAX_REDIRECTS) {\n throw new Error(`Too many redirects while downloading file: ${urlValue}`);\n }\n\n const urlObj = new URL(urlValue);\n ensureTmpDir();\n const filePath = buildTempFilePath(urlObj);\n\n return new Promise((resolve, reject) => {\n const fileStream = fs.createWriteStream(filePath);\n\n const request = https.get(urlObj, (response) => {\n const status = response.statusCode ?? 0;\n const location = response.headers.location;\n\n if (status >= 300 && status < 400 && location) {\n response.resume();\n fileStream.close(() => fs.unlink(filePath, () => {}));\n const nextUrl = new URL(location, urlObj).toString();\n downloadUrlToFile(nextUrl, redirectCount + 1).then(resolve).catch(reject);\n return;\n }\n\n if (status >= 400) {\n response.resume();\n fileStream.close(() => fs.unlink(filePath, () => {}));\n reject(new Error(`Failed to download file. HTTP ${status}`));\n return;\n }\n\n response.pipe(fileStream);\n });\n\n request.on(\"error\", (err) => {\n fileStream.close(() => fs.unlink(filePath, () => {}));\n reject(err);\n });\n\n fileStream.on(\"error\", (err) => {\n request.destroy();\n fs.unlink(filePath, () => {});\n reject(err);\n });\n\n fileStream.on(\"finish\", () => {\n fileStream.close(() => resolve(filePath));\n });\n });\n}\n\nfunction resolveFilePath(rawValue: string): string {\n const value = rawValue.startsWith(\"file:\") ? rawValue.slice(5) : rawValue;\n if (!value) {\n throw new Error('Invalid file reference. Use \"file:/absolute/path\".');\n }\n if (!path.isAbsolute(value)) {\n throw new Error(`File path must be absolute: ${value}`);\n }\n if (!fs.existsSync(value)) {\n throw new Error(`File does not exist: ${value}`);\n }\n return value;\n}\n\nasync function resolveFileReference(rawValue: string): Promise<{ filePath: string; source: \"file\" | \"url\"; url?: string }>{\n if (rawValue.startsWith(\"url:\")) {\n const urlValue = rawValue.slice(4);\n if (!urlValue) {\n throw new Error('Invalid url reference. Use \"url:https://...\".');\n }\n const filePath = await downloadUrlToFile(urlValue);\n return { filePath, source: \"url\", url: urlValue };\n }\n\n return { filePath: resolveFilePath(rawValue), source: \"file\" };\n}\n\nfunction formItemsFromRecord(record: Record<string, any>): ApiFormItem[] {\n return Object.entries(record).map(([name, value]) => {\n if (typeof value === \"string\" && (value.startsWith(\"file:\") || value.startsWith(\"url:\"))) {\n return { name, value, type: \"file\" };\n }\n return { name, value };\n });\n}\n\nasync function buildFormData(items: ApiFormItem[]): Promise<{ form: FormData; logItems: Record<string, any>[] }>{\n const form = new FormData();\n const logItems: Record<string, any>[] = [];\n\n for (const item of items) {\n if (!item?.name) {\n throw new Error('Form item is missing required \"name\".');\n }\n const rawValue = item.value;\n const isFile = item.type === \"file\" || (typeof rawValue === \"string\" && (rawValue.startsWith(\"file:\") || rawValue.startsWith(\"url:\")));\n\n if (isFile) {\n if (typeof rawValue !== \"string\") {\n throw new Error(`Form file value must be a string for field: ${item.name}`);\n }\n const resolved = await resolveFileReference(rawValue);\n const filename = item.filename ?? path.basename(resolved.filePath);\n form.append(item.name, fs.createReadStream(resolved.filePath), {\n filename,\n contentType: item.contentType,\n });\n logItems.push({\n name: item.name,\n type: \"file\",\n source: resolved.source,\n url: resolved.url,\n path: resolved.filePath,\n filename,\n contentType: item.contentType,\n });\n } else {\n // TODO: Need to test the JSON.stringify(rawValue) functionality here...\n // This scenario will happen only when we try to create embedded one-to-many or many-to-one objects in create API calls...\n const textValue = rawValue === undefined || rawValue === null ? \"\" : typeof rawValue === \"string\" ? rawValue : JSON.stringify(rawValue);\n form.append(item.name, textValue);\n logItems.push({\n name: item.name,\n type: \"text\",\n value: textValue,\n });\n }\n }\n\n return { form, logItems };\n}\n\nexport function registerApiRequestStep(registry: StepRegistry): void {\n registry.register(\"api.request\", async (ctx: TestContext, step: OpStep) => {\n if (!ctx.api) {\n throw new Error('Missing API adapter on context for op \"api.request\"');\n }\n\n const input = (step.with ?? {}) as ApiRequestInput;\n if (!input.method) {\n throw new Error('Missing \"method\" in step.with for op \"api.request\"');\n }\n if (!input.url) {\n throw new Error('Missing \"url\" in step.with for op \"api.request\"');\n }\n\n const rawFormData = input.formData ?? input.body;\n const formItems = Array.isArray(rawFormData) ? rawFormData : isPlainObject(rawFormData) ? formItemsFromRecord(rawFormData) : undefined;\n\n if (rawFormData !== undefined && !formItems) {\n throw new Error('formData/body must be an array of items or an object, for op \"api.request\".');\n }\n\n if (formItems && (input.json !== undefined || input.bodyText !== undefined)) {\n throw new Error('Use either formData/body or json/bodyText, not both, for op \"api.request\".');\n }\n\n // Original URL (may include query).\n const rawUrl = input.url;\n\n // Detect absolute URLs by scheme.\n const absolute = /^[a-z][a-z0-9+.-]*:/i.test(rawUrl);\n\n // Parse with base for relative URLs.\n const urlObj = new URL(rawUrl, \"http://solid.local\");\n\n // Parse URL query into object.\n const urlQuery = urlObj.search\n ? (qs.parse(urlObj.search, { ignoreQueryPrefix: true, allowDots: true, depth: 10 }) as Record<string, any>)\n : {}; // No query in URL.\n let stepQuery: Record<string, any> = {};\n if (input.query !== undefined) {\n stepQuery =\n typeof input.query === \"string\"\n // Parse query string.\n ? (qs.parse(input.query, { ignoreQueryPrefix: true, allowDots: true, depth: 10 }) as Record<string, any>)\n // Use object as-is.\n : (input.query as Record<string, any>);\n }\n\n // Step query overrides URL query.\n const mergedQuery = deepMerge(urlQuery, stepQuery);\n\n // Rebuild query string.\n const queryString = qs.stringify(mergedQuery, { addQueryPrefix: true, allowDots: true });\n\n // Apply merged query to URL.\n urlObj.search = queryString;\n const finalUrl = absolute\n // Preserve absolute URL.\n ? urlObj.toString()\n // Keep relative URL shape.\n : `${urlObj.pathname}${urlObj.search}${urlObj.hash}`;\n\n let formData: FormData | undefined;\n let formLogItems: Record<string, any>[] | undefined;\n if (formItems) {\n const built = await buildFormData(formItems);\n formData = built.form;\n formLogItems = built.logItems;\n }\n\n const req: ApiRequestOptions = {\n method: input.method,\n url: finalUrl,\n headers: input.headers,\n json: input.json,\n bodyText: input.bodyText,\n formData,\n };\n\n const startedAt = Date.now();\n const printApiLogs = ctx.options?.printApiLogs ?? false;\n const logName = `api.request ${req.method} ${finalUrl}`;\n const requestLog = stripUndefined({\n method: req.method,\n url: finalUrl,\n headers: req.headers,\n query: mergedQuery,\n queryString: urlObj.search ? urlObj.search.slice(1) : \"\",\n json: input.json,\n bodyText: input.bodyText,\n formData: formLogItems,\n });\n\n let response;\n try {\n response = await ctx.api.http(req);\n } catch (err: any) {\n if (printApiLogs) {\n attachJson(ctx, logName, {\n request: requestLog,\n durationMs: Date.now() - startedAt,\n error: err instanceof Error ? err.message : String(err),\n });\n }\n throw err;\n }\n\n const durationMs = Date.now() - startedAt;\n ctx.last = { ...(ctx.last ?? {}), apiResponse: response };\n\n if (printApiLogs) {\n const responseLog = stripUndefined({\n status: response.status,\n headers: response.headers,\n bodyJson: response.bodyJson,\n bodyText: response.bodyJson === undefined ? response.bodyText : undefined,\n });\n attachJson(ctx, logName, {\n request: requestLog,\n response: responseLog,\n durationMs,\n });\n }\n\n return {\n status: response.status,\n headers: response.headers,\n bodyText: response.bodyText,\n bodyJson: response.bodyJson,\n body: response.bodyJson ?? response.bodyText,\n };\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.step.d.ts","sourceRoot":"","sources":["../../../../src/testing/steps/assert/http.step.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAKxD,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAyBpE"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerHttpAssertSteps = registerHttpAssertSteps;
|
|
4
|
+
const attachments_1 = require("../../reporter/attachments");
|
|
5
|
+
function registerHttpAssertSteps(registry) {
|
|
6
|
+
registry.register("assert.httpStatus", async (ctx, step) => {
|
|
7
|
+
const input = (step.with ?? {});
|
|
8
|
+
const response = input.from ?? ctx.last?.apiResponse;
|
|
9
|
+
if (!response) {
|
|
10
|
+
throw new Error('Missing ApiResponse for op "assert.httpStatus"');
|
|
11
|
+
}
|
|
12
|
+
if (input.is === undefined) {
|
|
13
|
+
throw new Error('Missing "is" in step.with for op "assert.httpStatus"');
|
|
14
|
+
}
|
|
15
|
+
if (ctx.options?.printApiLogs && ctx.reporter.attach) {
|
|
16
|
+
(0, attachments_1.attachJson)(ctx, "apiResponse", response);
|
|
17
|
+
}
|
|
18
|
+
if (response.status !== input.is) {
|
|
19
|
+
const err = new Error(`Expected HTTP status ${input.is} but got ${response.status}`);
|
|
20
|
+
err.httpResponseBody = response.bodyText;
|
|
21
|
+
err.httpStatus = response.status;
|
|
22
|
+
err.httpExpectedStatus = input.is;
|
|
23
|
+
throw err;
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=http.step.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http.step.js","sourceRoot":"","sources":["../../../../src/testing/steps/assert/http.step.ts"],"names":[],"mappings":";;AAOA,0DAyBC;AA7BD,4DAAwD;AAIxD,SAAgB,uBAAuB,CAAC,QAAsB;IAC5D,QAAQ,CAAC,QAAQ,CAAC,mBAAmB,EAAE,KAAK,EAAE,GAAgB,EAAE,IAAY,EAAE,EAAE;QAC9E,MAAM,KAAK,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAoB,CAAC;QACnD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,IAAI,GAAG,CAAC,IAAI,EAAE,WAAW,CAAC;QACrD,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,KAAK,CAAC,EAAE,KAAK,SAAS,EAAE,CAAC;YAC3B,MAAM,IAAI,KAAK,CAAC,sDAAsD,CAAC,CAAC;QAC1E,CAAC;QAED,IAAI,GAAG,CAAC,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;YACrD,IAAA,wBAAU,EAAC,GAAG,EAAE,aAAa,EAAE,QAAQ,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC,EAAE,EAAE,CAAC;YACjC,MAAM,GAAG,GAAG,IAAI,KAAK,CACnB,wBAAwB,KAAK,CAAC,EAAE,YAAY,QAAQ,CAAC,MAAM,EAAE,CAC9D,CAAC;YACD,GAAW,CAAC,gBAAgB,GAAG,QAAQ,CAAC,QAAQ,CAAC;YACjD,GAAW,CAAC,UAAU,GAAG,QAAQ,CAAC,MAAM,CAAC;YACzC,GAAW,CAAC,kBAAkB,GAAG,KAAK,CAAC,EAAE,CAAC;YAC3C,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC","sourcesContent":["import type { ApiResponse, TestContext } from \"../../contracts/runtime-context.types\";\nimport type { OpStep } from \"../../contracts/testing-metadata.types\";\nimport { StepRegistry } from \"../../core/step-registry\";\nimport { attachJson } from \"../../reporter/attachments\";\n\ntype HttpStatusInput = { from?: ApiResponse; is: number };\n\nexport function registerHttpAssertSteps(registry: StepRegistry): void {\n registry.register(\"assert.httpStatus\", async (ctx: TestContext, step: OpStep) => {\n const input = (step.with ?? {}) as HttpStatusInput;\n const response = input.from ?? ctx.last?.apiResponse;\n if (!response) {\n throw new Error('Missing ApiResponse for op \"assert.httpStatus\"');\n }\n if (input.is === undefined) {\n throw new Error('Missing \"is\" in step.with for op \"assert.httpStatus\"');\n }\n\n if (ctx.options?.printApiLogs && ctx.reporter.attach) {\n attachJson(ctx, \"apiResponse\", response);\n }\n\n if (response.status !== input.is) {\n const err = new Error(\n `Expected HTTP status ${input.is} but got ${response.status}`,\n );\n (err as any).httpResponseBody = response.bodyText;\n (err as any).httpStatus = response.status;\n (err as any).httpExpectedStatus = input.is;\n throw err;\n }\n });\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/testing/steps/assert/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAKxD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,YAAY,GAAG,IAAI,CAIhE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerAssertSteps = registerAssertSteps;
|
|
4
|
+
const http_step_1 = require("./http.step");
|
|
5
|
+
const jsonpath_step_1 = require("./jsonpath.step");
|
|
6
|
+
const primitives_step_1 = require("./primitives.step");
|
|
7
|
+
function registerAssertSteps(registry) {
|
|
8
|
+
(0, primitives_step_1.registerPrimitiveAssertSteps)(registry);
|
|
9
|
+
(0, http_step_1.registerHttpAssertSteps)(registry);
|
|
10
|
+
(0, jsonpath_step_1.registerJsonPathAssertSteps)(registry);
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/testing/steps/assert/index.ts"],"names":[],"mappings":";;AAOA,kDAIC;AARD,2CAAsD;AACtD,mDAA8D;AAC9D,uDAAiE;AAEjE,SAAgB,mBAAmB,CAAC,QAAsB;IACxD,IAAA,8CAA4B,EAAC,QAAQ,CAAC,CAAC;IACvC,IAAA,mCAAuB,EAAC,QAAQ,CAAC,CAAC;IAClC,IAAA,2CAA2B,EAAC,QAAQ,CAAC,CAAC;AACxC,CAAC","sourcesContent":["// Purpose: Assert step registrations.\n\nimport { StepRegistry } from \"../../core/step-registry\";\nimport { registerHttpAssertSteps } from \"./http.step\";\nimport { registerJsonPathAssertSteps } from \"./jsonpath.step\";\nimport { registerPrimitiveAssertSteps } from \"./primitives.step\";\n\nexport function registerAssertSteps(registry: StepRegistry): void {\n registerPrimitiveAssertSteps(registry);\n registerHttpAssertSteps(registry);\n registerJsonPathAssertSteps(registry);\n}\n"]}
|