@jterrazz/test 3.2.0 → 3.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.cjs +2 -2
- package/dist/build.js +1 -1
- package/dist/index.cjs +274 -35
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +85 -20
- package/dist/index.d.ts +85 -20
- package/dist/index.js +273 -36
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/build.cjs
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
const require_chunk = require("./chunk.cjs");
|
|
2
2
|
const require_dist$5 = require("./dist.cjs");
|
|
3
|
+
let node_os = require("node:os");
|
|
4
|
+
node_os = require_chunk.__toESM(node_os);
|
|
3
5
|
let node_crypto = require("node:crypto");
|
|
4
6
|
node_crypto = require_chunk.__toESM(node_crypto);
|
|
5
7
|
let node_net = require("node:net");
|
|
6
8
|
node_net = require_chunk.__toESM(node_net);
|
|
7
|
-
let node_os = require("node:os");
|
|
8
|
-
node_os = require_chunk.__toESM(node_os);
|
|
9
9
|
//#region node_modules/graceful-fs/polyfills.js
|
|
10
10
|
var require_polyfills = /* @__PURE__ */ require_chunk.__commonJSMin(((exports, module) => {
|
|
11
11
|
var constants = require("constants");
|
package/dist/build.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { a as __toCommonJS, i as __require, n as __esmMin, r as __exportAll, t as __commonJSMin } from "./chunk.js";
|
|
2
2
|
import { t as require_dist$4 } from "./dist.js";
|
|
3
|
+
import os from "node:os";
|
|
3
4
|
import crypto from "node:crypto";
|
|
4
5
|
import net from "node:net";
|
|
5
|
-
import os from "node:os";
|
|
6
6
|
//#region node_modules/graceful-fs/polyfills.js
|
|
7
7
|
var require_polyfills = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
8
8
|
var constants = __require("constants");
|
package/dist/index.cjs
CHANGED
|
@@ -4,9 +4,10 @@ const require_dist$2 = require("./dist.cjs");
|
|
|
4
4
|
let mockdate = require("mockdate");
|
|
5
5
|
mockdate = require_chunk.__toESM(mockdate);
|
|
6
6
|
let vitest_mock_extended = require("vitest-mock-extended");
|
|
7
|
+
let node_fs = require("node:fs");
|
|
7
8
|
let node_path = require("node:path");
|
|
8
9
|
let node_child_process = require("node:child_process");
|
|
9
|
-
let
|
|
10
|
+
let node_os = require("node:os");
|
|
10
11
|
//#region src/mocking/mock-of-date.ts
|
|
11
12
|
const mockOfDate = mockdate.default;
|
|
12
13
|
//#endregion
|
|
@@ -281,6 +282,60 @@ function formatResponseDiff(file, expected, actual) {
|
|
|
281
282
|
}
|
|
282
283
|
return lines.join("\n");
|
|
283
284
|
}
|
|
285
|
+
function formatExitCodeError(expected, received, stdout, stderr) {
|
|
286
|
+
const lines = [];
|
|
287
|
+
lines.push(`Expected exit code: ${GREEN}${expected}${RESET}`);
|
|
288
|
+
lines.push(`Received exit code: ${RED}${received}${RESET}`);
|
|
289
|
+
if (stdout.trim()) {
|
|
290
|
+
lines.push("");
|
|
291
|
+
lines.push(`${DIM}stdout:${RESET}`);
|
|
292
|
+
for (const line of stdout.trim().split("\n").slice(-15)) lines.push(` ${DIM}${line}${RESET}`);
|
|
293
|
+
}
|
|
294
|
+
if (stderr.trim()) {
|
|
295
|
+
lines.push("");
|
|
296
|
+
lines.push(`${DIM}stderr:${RESET}`);
|
|
297
|
+
for (const line of stderr.trim().split("\n").slice(-15)) lines.push(` ${RED}${line}${RESET}`);
|
|
298
|
+
}
|
|
299
|
+
return lines.join("\n");
|
|
300
|
+
}
|
|
301
|
+
function formatStdoutDiff(file, expected, actual) {
|
|
302
|
+
const lines = [];
|
|
303
|
+
lines.push(`Output mismatch (${file})`);
|
|
304
|
+
lines.push("");
|
|
305
|
+
lines.push(`${GREEN}- Expected${RESET}`);
|
|
306
|
+
lines.push(`${RED}+ Received${RESET}`);
|
|
307
|
+
lines.push("");
|
|
308
|
+
const expectedLines = expected.split("\n");
|
|
309
|
+
const actualLines = actual.split("\n");
|
|
310
|
+
const maxLines = Math.max(expectedLines.length, actualLines.length);
|
|
311
|
+
for (let i = 0; i < maxLines; i++) {
|
|
312
|
+
const exp = expectedLines[i];
|
|
313
|
+
const act = actualLines[i];
|
|
314
|
+
if (exp === act) lines.push(` ${exp}`);
|
|
315
|
+
else {
|
|
316
|
+
if (exp !== void 0) lines.push(`${GREEN}- ${exp}${RESET}`);
|
|
317
|
+
if (act !== void 0) lines.push(`${RED}+ ${act}${RESET}`);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return lines.join("\n");
|
|
321
|
+
}
|
|
322
|
+
function formatFileMissing(path) {
|
|
323
|
+
return `Expected file to exist: ${RED}${path}${RESET}`;
|
|
324
|
+
}
|
|
325
|
+
function formatFileUnexpected(path) {
|
|
326
|
+
return `Expected file NOT to exist: ${RED}${path}${RESET}`;
|
|
327
|
+
}
|
|
328
|
+
function formatFileContentMismatch(path, expected, actual) {
|
|
329
|
+
const lines = [];
|
|
330
|
+
lines.push(`File "${path}" does not contain expected content`);
|
|
331
|
+
lines.push("");
|
|
332
|
+
lines.push(`${GREEN}Expected to contain:${RESET}`);
|
|
333
|
+
lines.push(` ${GREEN}${expected}${RESET}`);
|
|
334
|
+
lines.push("");
|
|
335
|
+
lines.push(`${RED}Actual content (first 20 lines):${RESET}`);
|
|
336
|
+
for (const line of actual.split("\n").slice(0, 20)) lines.push(` ${DIM}${line}${RESET}`);
|
|
337
|
+
return lines.join("\n");
|
|
338
|
+
}
|
|
284
339
|
function rowLabel(n) {
|
|
285
340
|
return n === 1 ? "1 row" : `${n} rows`;
|
|
286
341
|
}
|
|
@@ -4844,6 +4899,46 @@ var Orchestrator = class {
|
|
|
4844
4899
|
}
|
|
4845
4900
|
};
|
|
4846
4901
|
//#endregion
|
|
4902
|
+
//#region src/specification/adapters/exec.adapter.ts
|
|
4903
|
+
/**
|
|
4904
|
+
* Executes CLI commands via execSync.
|
|
4905
|
+
* Used by cli() for local command execution.
|
|
4906
|
+
*/
|
|
4907
|
+
var ExecAdapter = class {
|
|
4908
|
+
command;
|
|
4909
|
+
constructor(command) {
|
|
4910
|
+
this.command = command;
|
|
4911
|
+
}
|
|
4912
|
+
async exec(args, cwd) {
|
|
4913
|
+
const env = {
|
|
4914
|
+
...process.env,
|
|
4915
|
+
INIT_CWD: void 0
|
|
4916
|
+
};
|
|
4917
|
+
try {
|
|
4918
|
+
return {
|
|
4919
|
+
exitCode: 0,
|
|
4920
|
+
stdout: (0, node_child_process.execSync)(`${this.command} ${args}`, {
|
|
4921
|
+
cwd,
|
|
4922
|
+
encoding: "utf8",
|
|
4923
|
+
env,
|
|
4924
|
+
stdio: [
|
|
4925
|
+
"pipe",
|
|
4926
|
+
"pipe",
|
|
4927
|
+
"pipe"
|
|
4928
|
+
]
|
|
4929
|
+
}),
|
|
4930
|
+
stderr: ""
|
|
4931
|
+
};
|
|
4932
|
+
} catch (error) {
|
|
4933
|
+
return {
|
|
4934
|
+
exitCode: error.status ?? 1,
|
|
4935
|
+
stdout: error.stdout?.toString() ?? "",
|
|
4936
|
+
stderr: error.stderr?.toString() ?? ""
|
|
4937
|
+
};
|
|
4938
|
+
}
|
|
4939
|
+
}
|
|
4940
|
+
};
|
|
4941
|
+
//#endregion
|
|
4847
4942
|
//#region src/specification/adapters/fetch.adapter.ts
|
|
4848
4943
|
/**
|
|
4849
4944
|
* Server adapter for real HTTP — sends actual fetch requests.
|
|
@@ -4906,25 +5001,60 @@ var HonoAdapter = class {
|
|
|
4906
5001
|
//#endregion
|
|
4907
5002
|
//#region src/specification/specification.ts
|
|
4908
5003
|
var SpecificationResult = class {
|
|
4909
|
-
|
|
5004
|
+
commandResult;
|
|
4910
5005
|
config;
|
|
4911
|
-
testDir;
|
|
4912
5006
|
requestInfo;
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
this.
|
|
5007
|
+
response;
|
|
5008
|
+
testDir;
|
|
5009
|
+
workDir;
|
|
5010
|
+
constructor(options) {
|
|
5011
|
+
this.response = options.response;
|
|
5012
|
+
this.commandResult = options.commandResult;
|
|
5013
|
+
this.config = options.config;
|
|
5014
|
+
this.testDir = options.testDir;
|
|
5015
|
+
this.requestInfo = options.requestInfo;
|
|
5016
|
+
this.workDir = options.workDir;
|
|
4918
5017
|
}
|
|
4919
5018
|
expectStatus(code) {
|
|
5019
|
+
if (!this.response || !this.requestInfo) throw new Error("expectStatus requires an HTTP action (.get(), .post(), etc.)");
|
|
4920
5020
|
if (this.response.status !== code) throw new Error(formatStatusError(code, this.response.status, this.requestInfo, this.response.body));
|
|
4921
5021
|
return this;
|
|
4922
5022
|
}
|
|
4923
5023
|
expectResponse(file) {
|
|
5024
|
+
if (!this.response) throw new Error("expectResponse requires an HTTP action (.get(), .post(), etc.)");
|
|
4924
5025
|
const expected = JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "responses", file), "utf8"));
|
|
4925
5026
|
if (JSON.stringify(this.response.body) !== JSON.stringify(expected)) throw new Error(formatResponseDiff(file, expected, this.response.body));
|
|
4926
5027
|
return this;
|
|
4927
5028
|
}
|
|
5029
|
+
expectExitCode(code) {
|
|
5030
|
+
if (!this.commandResult) throw new Error("expectExitCode requires a CLI action (.exec())");
|
|
5031
|
+
if (this.commandResult.exitCode !== code) throw new Error(formatExitCodeError(code, this.commandResult.exitCode, this.commandResult.stdout, this.commandResult.stderr));
|
|
5032
|
+
return this;
|
|
5033
|
+
}
|
|
5034
|
+
expectStdout(file) {
|
|
5035
|
+
if (!this.commandResult) throw new Error("expectStdout requires a CLI action (.exec())");
|
|
5036
|
+
const expected = (0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "expected", file), "utf8").trim();
|
|
5037
|
+
const actual = this.commandResult.stdout.trim();
|
|
5038
|
+
if (actual !== expected) throw new Error(formatStdoutDiff(file, expected, actual));
|
|
5039
|
+
return this;
|
|
5040
|
+
}
|
|
5041
|
+
expectStdoutContains(str) {
|
|
5042
|
+
if (!this.commandResult) throw new Error("expectStdoutContains requires a CLI action (.exec())");
|
|
5043
|
+
if (!this.commandResult.stdout.includes(str)) throw new Error(`Expected stdout to contain: "${str}"\n\nActual stdout:\n${this.commandResult.stdout}`);
|
|
5044
|
+
return this;
|
|
5045
|
+
}
|
|
5046
|
+
expectStderr(file) {
|
|
5047
|
+
if (!this.commandResult) throw new Error("expectStderr requires a CLI action (.exec())");
|
|
5048
|
+
const expected = (0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "expected", file), "utf8").trim();
|
|
5049
|
+
const actual = this.commandResult.stderr.trim();
|
|
5050
|
+
if (actual !== expected) throw new Error(formatStdoutDiff(file, expected, actual));
|
|
5051
|
+
return this;
|
|
5052
|
+
}
|
|
5053
|
+
expectStderrContains(str) {
|
|
5054
|
+
if (!this.commandResult) throw new Error("expectStderrContains requires a CLI action (.exec())");
|
|
5055
|
+
if (!this.commandResult.stderr.includes(str)) throw new Error(`Expected stderr to contain: "${str}"\n\nActual stderr:\n${this.commandResult.stderr}`);
|
|
5056
|
+
return this;
|
|
5057
|
+
}
|
|
4928
5058
|
async expectTable(table, options) {
|
|
4929
5059
|
const db = this.resolveDatabase(options.service);
|
|
4930
5060
|
if (!db) throw new Error(options.service ? `expectTable requires database "${options.service}" but it was not found` : "expectTable requires a database adapter");
|
|
@@ -4932,18 +5062,40 @@ var SpecificationResult = class {
|
|
|
4932
5062
|
if (JSON.stringify(actual) !== JSON.stringify(options.rows)) throw new Error(formatTableDiff(table, options.columns, options.rows, actual));
|
|
4933
5063
|
return this;
|
|
4934
5064
|
}
|
|
5065
|
+
expectFile(path) {
|
|
5066
|
+
if (!(0, node_fs.existsSync)(this.resolveWorkPath(path))) throw new Error(formatFileMissing(path));
|
|
5067
|
+
return this;
|
|
5068
|
+
}
|
|
5069
|
+
expectNoFile(path) {
|
|
5070
|
+
if ((0, node_fs.existsSync)(this.resolveWorkPath(path))) throw new Error(formatFileUnexpected(path));
|
|
5071
|
+
return this;
|
|
5072
|
+
}
|
|
5073
|
+
expectFileContains(path, content) {
|
|
5074
|
+
const resolved = this.resolveWorkPath(path);
|
|
5075
|
+
if (!(0, node_fs.existsSync)(resolved)) throw new Error(formatFileMissing(path));
|
|
5076
|
+
const actual = (0, node_fs.readFileSync)(resolved, "utf8");
|
|
5077
|
+
if (!actual.includes(content)) throw new Error(formatFileContentMismatch(path, content, actual));
|
|
5078
|
+
return this;
|
|
5079
|
+
}
|
|
4935
5080
|
resolveDatabase(serviceName) {
|
|
4936
5081
|
if (serviceName && this.config.databases) return this.config.databases.get(serviceName);
|
|
4937
5082
|
return this.config.database;
|
|
4938
5083
|
}
|
|
5084
|
+
resolveWorkPath(path) {
|
|
5085
|
+
if (this.workDir) return (0, node_path.resolve)(this.workDir, path);
|
|
5086
|
+
return (0, node_path.resolve)(this.testDir, path);
|
|
5087
|
+
}
|
|
4939
5088
|
};
|
|
4940
5089
|
var SpecificationBuilder = class {
|
|
5090
|
+
commandArgs = null;
|
|
4941
5091
|
config;
|
|
4942
|
-
|
|
5092
|
+
fixtures = [];
|
|
4943
5093
|
label;
|
|
4944
|
-
seeds = [];
|
|
4945
5094
|
mocks = [];
|
|
5095
|
+
projectName = null;
|
|
4946
5096
|
request = null;
|
|
5097
|
+
seeds = [];
|
|
5098
|
+
testDir;
|
|
4947
5099
|
constructor(config, testDir, label) {
|
|
4948
5100
|
this.config = config;
|
|
4949
5101
|
this.testDir = testDir;
|
|
@@ -4956,6 +5108,14 @@ var SpecificationBuilder = class {
|
|
|
4956
5108
|
});
|
|
4957
5109
|
return this;
|
|
4958
5110
|
}
|
|
5111
|
+
fixture(file) {
|
|
5112
|
+
this.fixtures.push({ file });
|
|
5113
|
+
return this;
|
|
5114
|
+
}
|
|
5115
|
+
project(name) {
|
|
5116
|
+
this.projectName = name;
|
|
5117
|
+
return this;
|
|
5118
|
+
}
|
|
4959
5119
|
mock(file) {
|
|
4960
5120
|
this.mocks.push({ file });
|
|
4961
5121
|
return this;
|
|
@@ -4969,17 +5129,17 @@ var SpecificationBuilder = class {
|
|
|
4969
5129
|
}
|
|
4970
5130
|
post(path, bodyFile) {
|
|
4971
5131
|
this.request = {
|
|
5132
|
+
bodyFile,
|
|
4972
5133
|
method: "POST",
|
|
4973
|
-
path
|
|
4974
|
-
bodyFile
|
|
5134
|
+
path
|
|
4975
5135
|
};
|
|
4976
5136
|
return this;
|
|
4977
5137
|
}
|
|
4978
5138
|
put(path, bodyFile) {
|
|
4979
5139
|
this.request = {
|
|
5140
|
+
bodyFile,
|
|
4980
5141
|
method: "PUT",
|
|
4981
|
-
path
|
|
4982
|
-
bodyFile
|
|
5142
|
+
path
|
|
4983
5143
|
};
|
|
4984
5144
|
return this;
|
|
4985
5145
|
}
|
|
@@ -4990,8 +5150,17 @@ var SpecificationBuilder = class {
|
|
|
4990
5150
|
};
|
|
4991
5151
|
return this;
|
|
4992
5152
|
}
|
|
5153
|
+
exec(args) {
|
|
5154
|
+
this.commandArgs = args;
|
|
5155
|
+
return this;
|
|
5156
|
+
}
|
|
4993
5157
|
async run() {
|
|
4994
|
-
|
|
5158
|
+
const hasHttpAction = this.request !== null;
|
|
5159
|
+
const hasCliAction = this.commandArgs !== null;
|
|
5160
|
+
if (!hasHttpAction && !hasCliAction) throw new Error(`Specification "${this.label}": no action defined. Call .get(), .post(), .exec(), etc. before .run()`);
|
|
5161
|
+
if (hasHttpAction && hasCliAction) throw new Error(`Specification "${this.label}": cannot mix HTTP (.get/.post) and CLI (.exec) actions`);
|
|
5162
|
+
let workDir = null;
|
|
5163
|
+
if (hasCliAction) workDir = this.prepareWorkDir();
|
|
4995
5164
|
if (this.config.databases) for (const db of this.config.databases.values()) await db.reset();
|
|
4996
5165
|
else if (this.config.database) await this.config.database.reset();
|
|
4997
5166
|
for (const entry of this.seeds) {
|
|
@@ -5004,13 +5173,43 @@ var SpecificationBuilder = class {
|
|
|
5004
5173
|
const sql = (0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "seeds", entry.file), "utf8");
|
|
5005
5174
|
await db.seed(sql);
|
|
5006
5175
|
}
|
|
5176
|
+
if (this.fixtures.length > 0 && workDir) for (const entry of this.fixtures) (0, node_fs.cpSync)((0, node_path.resolve)(this.testDir, "fixtures", entry.file), (0, node_path.resolve)(workDir, entry.file), { recursive: true });
|
|
5007
5177
|
for (const entry of this.mocks) JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "mock", entry.file), "utf8"));
|
|
5178
|
+
if (hasHttpAction) return this.runHttpAction();
|
|
5179
|
+
return this.runCliAction(workDir);
|
|
5180
|
+
}
|
|
5181
|
+
prepareWorkDir() {
|
|
5182
|
+
const tempDir = (0, node_fs.mkdtempSync)((0, node_path.resolve)((0, node_os.tmpdir)(), "spec-cli-"));
|
|
5183
|
+
if (this.projectName && this.config.fixturesRoot) {
|
|
5184
|
+
const projectDir = (0, node_path.resolve)(this.config.fixturesRoot, this.projectName);
|
|
5185
|
+
if (!(0, node_fs.existsSync)(projectDir)) throw new Error(`project("${this.projectName}"): fixture project not found at ${projectDir}`);
|
|
5186
|
+
(0, node_fs.cpSync)(projectDir, tempDir, { recursive: true });
|
|
5187
|
+
}
|
|
5188
|
+
return tempDir;
|
|
5189
|
+
}
|
|
5190
|
+
async runHttpAction() {
|
|
5191
|
+
if (!this.config.server) throw new Error("HTTP actions require a server adapter (use integration() or e2e())");
|
|
5008
5192
|
let body;
|
|
5009
5193
|
if (this.request.bodyFile) body = JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "requests", this.request.bodyFile), "utf8"));
|
|
5010
|
-
|
|
5011
|
-
|
|
5012
|
-
|
|
5013
|
-
|
|
5194
|
+
const response = await this.config.server.request(this.request.method, this.request.path, body);
|
|
5195
|
+
return new SpecificationResult({
|
|
5196
|
+
config: this.config,
|
|
5197
|
+
requestInfo: {
|
|
5198
|
+
body,
|
|
5199
|
+
method: this.request.method,
|
|
5200
|
+
path: this.request.path
|
|
5201
|
+
},
|
|
5202
|
+
response,
|
|
5203
|
+
testDir: this.testDir
|
|
5204
|
+
});
|
|
5205
|
+
}
|
|
5206
|
+
async runCliAction(workDir) {
|
|
5207
|
+
if (!this.config.command) throw new Error("CLI actions require a command adapter (use cli())");
|
|
5208
|
+
return new SpecificationResult({
|
|
5209
|
+
commandResult: await this.config.command.exec(this.commandArgs, workDir),
|
|
5210
|
+
config: this.config,
|
|
5211
|
+
testDir: this.testDir,
|
|
5212
|
+
workDir
|
|
5014
5213
|
});
|
|
5015
5214
|
}
|
|
5016
5215
|
};
|
|
@@ -5059,21 +5258,25 @@ function resolveProjectRoot(root) {
|
|
|
5059
5258
|
return (0, node_path.resolve)(process.cwd(), root);
|
|
5060
5259
|
}
|
|
5061
5260
|
/**
|
|
5261
|
+
* Resolve a CLI command — checks node_modules/.bin, then treats as absolute/PATH.
|
|
5262
|
+
*/
|
|
5263
|
+
function resolveCommand(command, root) {
|
|
5264
|
+
if ((0, node_path.isAbsolute)(command)) return command;
|
|
5265
|
+
const binPath = (0, node_path.resolve)(root, "node_modules/.bin", command);
|
|
5266
|
+
if ((0, node_fs.existsSync)(binPath)) return binPath;
|
|
5267
|
+
const cwdBinPath = (0, node_path.resolve)(process.cwd(), "node_modules/.bin", command);
|
|
5268
|
+
if ((0, node_fs.existsSync)(cwdBinPath)) return cwdBinPath;
|
|
5269
|
+
return command;
|
|
5270
|
+
}
|
|
5271
|
+
/**
|
|
5062
5272
|
* Create an integration specification runner.
|
|
5063
5273
|
* Starts infra containers via testcontainers, app runs in-process.
|
|
5064
|
-
*
|
|
5065
|
-
* @example
|
|
5066
|
-
* const db = postgres({ compose: "db" });
|
|
5067
|
-
* export const spec = await integration({
|
|
5068
|
-
* services: [db],
|
|
5069
|
-
* app: () => createApp({ databaseUrl: db.connectionString }),
|
|
5070
|
-
* });
|
|
5071
5274
|
*/
|
|
5072
5275
|
async function integration(options) {
|
|
5073
5276
|
const orchestrator = new Orchestrator({
|
|
5074
|
-
services: options.services,
|
|
5075
5277
|
mode: "integration",
|
|
5076
|
-
root: resolveProjectRoot(options.root)
|
|
5278
|
+
root: resolveProjectRoot(options.root),
|
|
5279
|
+
services: options.services
|
|
5077
5280
|
});
|
|
5078
5281
|
await orchestrator.start();
|
|
5079
5282
|
const app = options.app();
|
|
@@ -5091,17 +5294,12 @@ async function integration(options) {
|
|
|
5091
5294
|
/**
|
|
5092
5295
|
* Create an E2E specification runner.
|
|
5093
5296
|
* Starts full docker compose stack. App URL and database auto-detected.
|
|
5094
|
-
*
|
|
5095
|
-
* @example
|
|
5096
|
-
* export const spec = await e2e({
|
|
5097
|
-
* root: "../fixtures/app",
|
|
5098
|
-
* });
|
|
5099
5297
|
*/
|
|
5100
5298
|
async function e2e(options = {}) {
|
|
5101
5299
|
const orchestrator = new Orchestrator({
|
|
5102
|
-
services: [],
|
|
5103
5300
|
mode: "e2e",
|
|
5104
|
-
root: resolveProjectRoot(options.root)
|
|
5301
|
+
root: resolveProjectRoot(options.root),
|
|
5302
|
+
services: []
|
|
5105
5303
|
});
|
|
5106
5304
|
await orchestrator.startCompose();
|
|
5107
5305
|
const appUrl = orchestrator.getAppUrl();
|
|
@@ -5117,10 +5315,51 @@ async function e2e(options = {}) {
|
|
|
5117
5315
|
runner.orchestrator = orchestrator;
|
|
5118
5316
|
return runner;
|
|
5119
5317
|
}
|
|
5318
|
+
/**
|
|
5319
|
+
* Create a CLI specification runner.
|
|
5320
|
+
* Runs CLI commands against fixture projects. Optionally starts infrastructure.
|
|
5321
|
+
*
|
|
5322
|
+
* @example
|
|
5323
|
+
* export const spec = await cli({
|
|
5324
|
+
* command: resolve(import.meta.dirname, "../../bin/my-cli.sh"),
|
|
5325
|
+
* root: "../fixtures",
|
|
5326
|
+
* });
|
|
5327
|
+
*/
|
|
5328
|
+
async function cli(options) {
|
|
5329
|
+
const root = resolveProjectRoot(options.root);
|
|
5330
|
+
const command = resolveCommand(options.command, root);
|
|
5331
|
+
let orchestrator = null;
|
|
5332
|
+
let database;
|
|
5333
|
+
let databases;
|
|
5334
|
+
if (options.services?.length) {
|
|
5335
|
+
orchestrator = new Orchestrator({
|
|
5336
|
+
mode: "integration",
|
|
5337
|
+
root,
|
|
5338
|
+
services: options.services
|
|
5339
|
+
});
|
|
5340
|
+
await orchestrator.start();
|
|
5341
|
+
database = orchestrator.getDatabase() ?? void 0;
|
|
5342
|
+
const dbMap = orchestrator.getDatabases();
|
|
5343
|
+
databases = dbMap.size > 0 ? dbMap : void 0;
|
|
5344
|
+
}
|
|
5345
|
+
const runner = createSpecificationRunner({
|
|
5346
|
+
command: new ExecAdapter(command),
|
|
5347
|
+
database,
|
|
5348
|
+
databases,
|
|
5349
|
+
fixturesRoot: root
|
|
5350
|
+
});
|
|
5351
|
+
runner.cleanup = async () => {
|
|
5352
|
+
if (orchestrator) await orchestrator.stop();
|
|
5353
|
+
};
|
|
5354
|
+
runner.orchestrator = orchestrator;
|
|
5355
|
+
return runner;
|
|
5356
|
+
}
|
|
5120
5357
|
//#endregion
|
|
5358
|
+
exports.ExecAdapter = ExecAdapter;
|
|
5121
5359
|
exports.FetchAdapter = FetchAdapter;
|
|
5122
5360
|
exports.HonoAdapter = HonoAdapter;
|
|
5123
5361
|
exports.Orchestrator = Orchestrator;
|
|
5362
|
+
exports.cli = cli;
|
|
5124
5363
|
exports.e2e = e2e;
|
|
5125
5364
|
exports.integration = integration;
|
|
5126
5365
|
exports.mockOf = mockOf;
|