@jterrazz/test 3.5.0 → 4.0.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/README.md +39 -33
- package/dist/index.cjs +227 -61
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +102 -16
- package/dist/index.d.ts +102 -16
- package/dist/index.js +227 -61
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -39,8 +39,8 @@ test("creates a user", async () => {
|
|
|
39
39
|
.run();
|
|
40
40
|
|
|
41
41
|
// Then — user created
|
|
42
|
-
result.
|
|
43
|
-
await result.
|
|
42
|
+
result.status.toBe(201);
|
|
43
|
+
await result.table("users").toMatch({
|
|
44
44
|
columns: ["name"],
|
|
45
45
|
rows: [["Alice"], ["Bob"]],
|
|
46
46
|
});
|
|
@@ -69,12 +69,11 @@ test("builds the project", async () => {
|
|
|
69
69
|
const result = await spec("build").project("sample-app").exec("build").run();
|
|
70
70
|
|
|
71
71
|
// Then — ESM output with source maps
|
|
72
|
-
result
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.expectFileContains("dist/index.js", "Hello");
|
|
72
|
+
result.exitCode.toBe(0);
|
|
73
|
+
result.stdout.toContain("Build completed");
|
|
74
|
+
result.file("dist/index.js").toExist();
|
|
75
|
+
result.file("dist/index.cjs").not.toExist();
|
|
76
|
+
result.file("dist/index.js").toContain("Hello");
|
|
78
77
|
});
|
|
79
78
|
```
|
|
80
79
|
|
|
@@ -166,38 +165,46 @@ Every test follows the same pattern: `spec("label") → setup → action → ass
|
|
|
166
165
|
|
|
167
166
|
### Assertions
|
|
168
167
|
|
|
169
|
-
|
|
168
|
+
Assertions use a scoped API: `result.{scope}.{assertion}`. Database assertions (`result.table()`) are async.
|
|
170
169
|
|
|
171
170
|
**HTTP-specific:**
|
|
172
171
|
|
|
173
|
-
| Method
|
|
174
|
-
|
|
|
175
|
-
|
|
|
176
|
-
|
|
|
172
|
+
| Method | Description |
|
|
173
|
+
| ------------------------------------------ | -------------------------------------------------- |
|
|
174
|
+
| `result.status.toBe(code)` | Assert HTTP status code |
|
|
175
|
+
| `result.response.toMatchFile("file.json")` | Assert response body matches `responses/file.json` |
|
|
177
176
|
|
|
178
177
|
**CLI-specific:**
|
|
179
178
|
|
|
180
|
-
| Method
|
|
181
|
-
|
|
|
182
|
-
|
|
|
183
|
-
|
|
|
184
|
-
|
|
|
185
|
-
|
|
|
186
|
-
|
|
|
179
|
+
| Method | Description |
|
|
180
|
+
| --------------------------------------------------- | -------------------------------------------------- |
|
|
181
|
+
| `result.exitCode.toBe(code)` | Assert process exit code |
|
|
182
|
+
| `result.stdout.toContain(str)` | Assert stdout contains string |
|
|
183
|
+
| `result.stdout.not.toContain(str)` | Assert stdout does not contain string |
|
|
184
|
+
| `result.stdout.toContain(str, { near: "ctx" })` | Assert stdout contains string near context |
|
|
185
|
+
| `result.stderr.toContain(str)` | Assert stderr contains string |
|
|
186
|
+
| `result.stderr.not.toContain(str)` | Assert stderr does not contain string |
|
|
187
|
+
| `result.stderr.not.toContain(str, { near: "ctx" })` | Assert stderr does not contain string near context |
|
|
188
|
+
| `result.stdout.toMatch(/regex/)` | Assert stdout matches regex |
|
|
189
|
+
| `result.stdout.toMatchFile("file.txt")` | Assert stdout matches `expected/file.txt` |
|
|
190
|
+
| `result.stderr.toMatchFile("file.txt")` | Assert stderr matches `expected/file.txt` |
|
|
191
|
+
| `result.stdout.toBeEmpty()` | Assert stdout is empty |
|
|
187
192
|
|
|
188
193
|
**Cross-mode:**
|
|
189
194
|
|
|
190
|
-
| Method
|
|
191
|
-
|
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
|
|
|
195
|
-
|
|
|
196
|
-
|
|
|
195
|
+
| Method | Description |
|
|
196
|
+
| ------------------------------------------------------------------ | --------------------------------------- |
|
|
197
|
+
| `await result.table(name).toMatch({ columns, rows })` | Assert database table contents |
|
|
198
|
+
| `await result.table(name, { service }).toMatch({ columns, rows })` | Assert on a specific database |
|
|
199
|
+
| `await result.table(name).toBeEmpty()` | Assert database table is empty |
|
|
200
|
+
| `result.file(path).toExist()` | Assert file exists in working directory |
|
|
201
|
+
| `result.file(path).not.toExist()` | Assert file does not exist |
|
|
202
|
+
| `result.file(path).toContain(content)` | Assert file contains string |
|
|
203
|
+
| `result.file(path).toMatch(/regex/)` | Assert file content matches regex |
|
|
197
204
|
|
|
198
205
|
## Multi-database support
|
|
199
206
|
|
|
200
|
-
When multiple databases are declared, `seed()` and `
|
|
207
|
+
When multiple databases are declared, `seed()` and `result.table()` accept `{ service: "name" }` to target a specific database by its compose name. Without `service`, both default to the first postgres.
|
|
201
208
|
|
|
202
209
|
```typescript
|
|
203
210
|
const db = postgres({ compose: "db" });
|
|
@@ -214,11 +221,10 @@ const result = await spec("cross-db")
|
|
|
214
221
|
.post("/users", "request.json")
|
|
215
222
|
.run();
|
|
216
223
|
|
|
217
|
-
await result.
|
|
218
|
-
await result.
|
|
224
|
+
await result.table("users").toMatch({ columns: ["name"], rows: [["Alice"]] });
|
|
225
|
+
await result.table("events", { service: "analytics-db" }).toMatch({
|
|
219
226
|
columns: ["type"],
|
|
220
227
|
rows: [["user_created"]],
|
|
221
|
-
service: "analytics-db",
|
|
222
228
|
});
|
|
223
229
|
```
|
|
224
230
|
|
|
@@ -300,8 +306,8 @@ test("creates a user and returns 201", async () => {
|
|
|
300
306
|
.run();
|
|
301
307
|
|
|
302
308
|
// Then — user created with all three in table
|
|
303
|
-
result.
|
|
304
|
-
await result.
|
|
309
|
+
result.status.toBe(201);
|
|
310
|
+
await result.table("users").toMatch({
|
|
305
311
|
columns: ["name"],
|
|
306
312
|
rows: [["Alice"], ["Bob"], ["Charlie"]],
|
|
307
313
|
});
|
package/dist/index.cjs
CHANGED
|
@@ -861,92 +861,258 @@ var HonoAdapter = class {
|
|
|
861
861
|
}
|
|
862
862
|
};
|
|
863
863
|
//#endregion
|
|
864
|
+
//#region src/specification/assertions/base.ts
|
|
865
|
+
/**
|
|
866
|
+
* Base assertion that handles .not negation.
|
|
867
|
+
* Subclasses call this.assert(condition, message, negatedMessage) for each predicate.
|
|
868
|
+
*/
|
|
869
|
+
var BaseAssertion = class {
|
|
870
|
+
negated = false;
|
|
871
|
+
get not() {
|
|
872
|
+
const clone = Object.create(Object.getPrototypeOf(this));
|
|
873
|
+
Object.assign(clone, this);
|
|
874
|
+
clone.negated = !this.negated;
|
|
875
|
+
return clone;
|
|
876
|
+
}
|
|
877
|
+
assert(condition, message, negatedMessage) {
|
|
878
|
+
if (this.negated) {
|
|
879
|
+
if (condition) throw new Error(negatedMessage);
|
|
880
|
+
} else if (!condition) throw new Error(message);
|
|
881
|
+
}
|
|
882
|
+
};
|
|
883
|
+
//#endregion
|
|
884
|
+
//#region src/specification/assertions/file.ts
|
|
885
|
+
/**
|
|
886
|
+
* Assertions on a file in the working directory.
|
|
887
|
+
* Usage: result.file("dist/index.js").toExist()
|
|
888
|
+
*/
|
|
889
|
+
var FileAssertion = class extends BaseAssertion {
|
|
890
|
+
filePath;
|
|
891
|
+
resolvedPath;
|
|
892
|
+
constructor(filePath, workDir) {
|
|
893
|
+
super();
|
|
894
|
+
this.filePath = filePath;
|
|
895
|
+
this.resolvedPath = (0, node_path.resolve)(workDir, filePath);
|
|
896
|
+
}
|
|
897
|
+
toExist() {
|
|
898
|
+
const exists = (0, node_fs.existsSync)(this.resolvedPath);
|
|
899
|
+
this.assert(exists, formatFileMissing(this.filePath), formatFileUnexpected(this.filePath));
|
|
900
|
+
}
|
|
901
|
+
toContain(expected) {
|
|
902
|
+
if (!(0, node_fs.existsSync)(this.resolvedPath)) {
|
|
903
|
+
if (this.negated) return;
|
|
904
|
+
throw new Error(formatFileMissing(this.filePath));
|
|
905
|
+
}
|
|
906
|
+
const content = (0, node_fs.readFileSync)(this.resolvedPath, "utf8");
|
|
907
|
+
const found = content.includes(expected);
|
|
908
|
+
this.assert(found, formatFileContentMismatch(this.filePath, expected, content), `Expected file "${this.filePath}" NOT to contain "${expected}"`);
|
|
909
|
+
}
|
|
910
|
+
toMatch(pattern) {
|
|
911
|
+
if (!(0, node_fs.existsSync)(this.resolvedPath)) {
|
|
912
|
+
if (this.negated) return;
|
|
913
|
+
throw new Error(formatFileMissing(this.filePath));
|
|
914
|
+
}
|
|
915
|
+
const content = (0, node_fs.readFileSync)(this.resolvedPath, "utf8");
|
|
916
|
+
const found = pattern.test(content);
|
|
917
|
+
this.assert(found, `Expected file "${this.filePath}" to match: ${pattern}\n\nActual content:\n${content.slice(0, 500)}`, `Expected file "${this.filePath}" NOT to match: ${pattern}`);
|
|
918
|
+
}
|
|
919
|
+
};
|
|
920
|
+
//#endregion
|
|
921
|
+
//#region src/specification/assertions/response.ts
|
|
922
|
+
/**
|
|
923
|
+
* Assertions on an HTTP response body.
|
|
924
|
+
* Usage: result.response.toMatchFile("expected.json")
|
|
925
|
+
*/
|
|
926
|
+
var ResponseAssertion = class extends BaseAssertion {
|
|
927
|
+
body;
|
|
928
|
+
testDir;
|
|
929
|
+
constructor(body, testDir) {
|
|
930
|
+
super();
|
|
931
|
+
this.body = body;
|
|
932
|
+
this.testDir = testDir;
|
|
933
|
+
}
|
|
934
|
+
toMatchFile(file) {
|
|
935
|
+
const expected = JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "responses", file), "utf8"));
|
|
936
|
+
const match = JSON.stringify(this.body) === JSON.stringify(expected);
|
|
937
|
+
this.assert(match, formatResponseDiff(file, expected, this.body), `Expected response NOT to match file "${file}", but it did`);
|
|
938
|
+
}
|
|
939
|
+
toContain(subset) {
|
|
940
|
+
const bodyStr = JSON.stringify(this.body);
|
|
941
|
+
const subsetStr = JSON.stringify(subset);
|
|
942
|
+
const bodyObj = typeof this.body === "object" && this.body !== null ? this.body : {};
|
|
943
|
+
const match = Object.entries(subset).every(([key, value]) => JSON.stringify(bodyObj[key]) === JSON.stringify(value));
|
|
944
|
+
this.assert(match, `Expected response to contain: ${subsetStr}\n\nActual response:\n${bodyStr}`, `Expected response NOT to contain: ${subsetStr}`);
|
|
945
|
+
}
|
|
946
|
+
};
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region src/specification/assertions/string.ts
|
|
949
|
+
/**
|
|
950
|
+
* Assertions on a string (stdout, stderr, response body).
|
|
951
|
+
* Usage: result.stdout.toContain("hello")
|
|
952
|
+
*/
|
|
953
|
+
var StringAssertion = class extends BaseAssertion {
|
|
954
|
+
actual;
|
|
955
|
+
label;
|
|
956
|
+
testDir;
|
|
957
|
+
constructor(actual, label, testDir) {
|
|
958
|
+
super();
|
|
959
|
+
this.actual = actual;
|
|
960
|
+
this.label = label;
|
|
961
|
+
this.testDir = testDir;
|
|
962
|
+
}
|
|
963
|
+
toContain(expected, options) {
|
|
964
|
+
if (options?.near) {
|
|
965
|
+
const found = this.containsNear(expected, options.near);
|
|
966
|
+
this.assert(found, `Expected ${this.label} to contain "${expected}" near "${options.near}"\n\n${this.label}:\n${this.truncate(this.actual)}`, `Expected ${this.label} NOT to contain "${expected}" near "${options.near}", but it was found`);
|
|
967
|
+
} else {
|
|
968
|
+
const found = this.actual.includes(expected);
|
|
969
|
+
this.assert(found, `Expected ${this.label} to contain: "${expected}"\n\nActual ${this.label}:\n${this.truncate(this.actual)}`, `Expected ${this.label} NOT to contain: "${expected}"`);
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
toMatch(pattern) {
|
|
973
|
+
const found = pattern.test(this.actual);
|
|
974
|
+
this.assert(found, `Expected ${this.label} to match: ${pattern}\n\nActual ${this.label}:\n${this.truncate(this.actual)}`, `Expected ${this.label} NOT to match: ${pattern}`);
|
|
975
|
+
}
|
|
976
|
+
toMatchFile(file) {
|
|
977
|
+
if (!this.testDir) throw new Error("toMatchFile requires a test directory context");
|
|
978
|
+
const expected = (0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "expected", file), "utf8").trim();
|
|
979
|
+
const actual = this.actual.trim();
|
|
980
|
+
const match = actual === expected;
|
|
981
|
+
this.assert(match, formatStdoutDiff(file, expected, actual), `Expected ${this.label} NOT to match file "${file}", but it did`);
|
|
982
|
+
}
|
|
983
|
+
toBeEmpty() {
|
|
984
|
+
const empty = this.actual.trim() === "";
|
|
985
|
+
this.assert(empty, `Expected ${this.label} to be empty\n\nActual ${this.label}:\n${this.truncate(this.actual)}`, `Expected ${this.label} NOT to be empty`);
|
|
986
|
+
}
|
|
987
|
+
containsNear(target, near, proximity = 500) {
|
|
988
|
+
const clean = this.stripAnsi(this.actual);
|
|
989
|
+
const nearLower = near.toLowerCase();
|
|
990
|
+
const targetLower = target.toLowerCase();
|
|
991
|
+
let searchFrom = 0;
|
|
992
|
+
while (true) {
|
|
993
|
+
const idx = clean.toLowerCase().indexOf(nearLower, searchFrom);
|
|
994
|
+
if (idx === -1) break;
|
|
995
|
+
const windowStart = Math.max(0, idx - proximity);
|
|
996
|
+
const windowEnd = Math.min(clean.length, idx + nearLower.length + proximity);
|
|
997
|
+
if (clean.substring(windowStart, windowEnd).toLowerCase().includes(targetLower)) return true;
|
|
998
|
+
searchFrom = idx + 1;
|
|
999
|
+
}
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
stripAnsi(str) {
|
|
1003
|
+
return str.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1004
|
+
}
|
|
1005
|
+
truncate(str, maxLines = 20) {
|
|
1006
|
+
const lines = str.split("\n");
|
|
1007
|
+
if (lines.length <= maxLines) return str;
|
|
1008
|
+
return `${lines.slice(0, maxLines).join("\n")}\n... (${lines.length - maxLines} more lines)`;
|
|
1009
|
+
}
|
|
1010
|
+
};
|
|
1011
|
+
//#endregion
|
|
1012
|
+
//#region src/specification/assertions/table.ts
|
|
1013
|
+
/**
|
|
1014
|
+
* Assertions on a database table.
|
|
1015
|
+
* Usage: await result.table("users").toMatch({ columns: ["name"], rows: [["Alice"]] })
|
|
1016
|
+
*/
|
|
1017
|
+
var TableAssertion = class extends BaseAssertion {
|
|
1018
|
+
tableName;
|
|
1019
|
+
db;
|
|
1020
|
+
constructor(tableName, db) {
|
|
1021
|
+
super();
|
|
1022
|
+
this.tableName = tableName;
|
|
1023
|
+
this.db = db;
|
|
1024
|
+
}
|
|
1025
|
+
async toMatch(expected) {
|
|
1026
|
+
const actual = await this.db.query(this.tableName, expected.columns);
|
|
1027
|
+
const match = JSON.stringify(actual) === JSON.stringify(expected.rows);
|
|
1028
|
+
this.assert(match, formatTableDiff(this.tableName, expected.columns, expected.rows, actual), `Expected table "${this.tableName}" NOT to match, but it did`);
|
|
1029
|
+
}
|
|
1030
|
+
async toBeEmpty() {
|
|
1031
|
+
const actual = await this.db.query(this.tableName, ["*"]);
|
|
1032
|
+
const empty = actual.length === 0;
|
|
1033
|
+
this.assert(empty, `Expected table "${this.tableName}" to be empty, but it has ${actual.length} rows`, `Expected table "${this.tableName}" NOT to be empty, but it is`);
|
|
1034
|
+
}
|
|
1035
|
+
};
|
|
1036
|
+
//#endregion
|
|
1037
|
+
//#region src/specification/assertions/value.ts
|
|
1038
|
+
/**
|
|
1039
|
+
* Assertions on a single value (exit code, status code).
|
|
1040
|
+
* Usage: result.exitCode.toBe(0)
|
|
1041
|
+
*/
|
|
1042
|
+
var ValueAssertion = class extends BaseAssertion {
|
|
1043
|
+
actual;
|
|
1044
|
+
label;
|
|
1045
|
+
context;
|
|
1046
|
+
constructor(actual, label, context) {
|
|
1047
|
+
super();
|
|
1048
|
+
this.actual = actual;
|
|
1049
|
+
this.label = label;
|
|
1050
|
+
this.context = context;
|
|
1051
|
+
}
|
|
1052
|
+
toBe(expected) {
|
|
1053
|
+
const match = this.actual === expected;
|
|
1054
|
+
let message;
|
|
1055
|
+
if (this.label === "exit code" && this.context?.stdout !== void 0) message = formatExitCodeError(expected, this.actual, this.context.stdout ?? "", this.context.stderr ?? "");
|
|
1056
|
+
else if (this.label === "status" && this.context?.request) message = formatStatusError(expected, this.actual, this.context.request, this.context.responseBody);
|
|
1057
|
+
else message = `Expected ${this.label}: ${expected}\nReceived ${this.label}: ${this.actual}`;
|
|
1058
|
+
this.assert(match, message, `Expected ${this.label} NOT to be ${expected}, but it was`);
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
//#endregion
|
|
864
1062
|
//#region src/specification/specification.ts
|
|
865
1063
|
var SpecificationResult = class {
|
|
866
1064
|
commandResult;
|
|
867
1065
|
config;
|
|
868
1066
|
requestInfo;
|
|
869
|
-
|
|
1067
|
+
responseData;
|
|
870
1068
|
testDir;
|
|
871
1069
|
workDir;
|
|
872
1070
|
constructor(options) {
|
|
873
|
-
this.
|
|
1071
|
+
this.responseData = options.response;
|
|
874
1072
|
this.commandResult = options.commandResult;
|
|
875
1073
|
this.config = options.config;
|
|
876
1074
|
this.testDir = options.testDir;
|
|
877
1075
|
this.requestInfo = options.requestInfo;
|
|
878
1076
|
this.workDir = options.workDir;
|
|
879
1077
|
}
|
|
880
|
-
|
|
881
|
-
if (!this.
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
if (!this.response) throw new Error("expectResponse requires an HTTP action (.get(), .post(), etc.)");
|
|
887
|
-
const expected = JSON.parse((0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "responses", file), "utf8"));
|
|
888
|
-
if (JSON.stringify(this.response.body) !== JSON.stringify(expected)) throw new Error(formatResponseDiff(file, expected, this.response.body));
|
|
889
|
-
return this;
|
|
890
|
-
}
|
|
891
|
-
expectExitCode(code) {
|
|
892
|
-
if (!this.commandResult) throw new Error("expectExitCode requires a CLI action (.exec())");
|
|
893
|
-
if (this.commandResult.exitCode !== code) throw new Error(formatExitCodeError(code, this.commandResult.exitCode, this.commandResult.stdout, this.commandResult.stderr));
|
|
894
|
-
return this;
|
|
895
|
-
}
|
|
896
|
-
expectStdout(file) {
|
|
897
|
-
if (!this.commandResult) throw new Error("expectStdout requires a CLI action (.exec())");
|
|
898
|
-
const expected = (0, node_fs.readFileSync)((0, node_path.resolve)(this.testDir, "expected", file), "utf8").trim();
|
|
899
|
-
const actual = this.commandResult.stdout.trim();
|
|
900
|
-
if (actual !== expected) throw new Error(formatStdoutDiff(file, expected, actual));
|
|
901
|
-
return this;
|
|
902
|
-
}
|
|
903
|
-
expectStdoutContains(str) {
|
|
904
|
-
if (!this.commandResult) throw new Error("expectStdoutContains requires a CLI action (.exec())");
|
|
905
|
-
if (!this.commandResult.stdout.includes(str)) throw new Error(`Expected stdout to contain: "${str}"\n\nActual stdout:\n${this.commandResult.stdout}`);
|
|
906
|
-
return this;
|
|
1078
|
+
get exitCode() {
|
|
1079
|
+
if (!this.commandResult) throw new Error(".exitCode requires a CLI action (.exec())");
|
|
1080
|
+
return new ValueAssertion(this.commandResult.exitCode, "exit code", {
|
|
1081
|
+
stderr: this.commandResult.stderr,
|
|
1082
|
+
stdout: this.commandResult.stdout
|
|
1083
|
+
});
|
|
907
1084
|
}
|
|
908
|
-
|
|
909
|
-
if (!this.
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
1085
|
+
get status() {
|
|
1086
|
+
if (!this.responseData || !this.requestInfo) throw new Error(".status requires an HTTP action (.get(), .post(), etc.)");
|
|
1087
|
+
return new ValueAssertion(this.responseData.status, "status", {
|
|
1088
|
+
request: this.requestInfo,
|
|
1089
|
+
responseBody: this.responseData.body
|
|
1090
|
+
});
|
|
914
1091
|
}
|
|
915
|
-
|
|
916
|
-
if (!this.
|
|
917
|
-
|
|
918
|
-
return this;
|
|
1092
|
+
get response() {
|
|
1093
|
+
if (!this.responseData) throw new Error(".response requires an HTTP action (.get(), .post(), etc.)");
|
|
1094
|
+
return new ResponseAssertion(this.responseData.body, this.testDir);
|
|
919
1095
|
}
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
const actual = await db.query(table, options.columns);
|
|
924
|
-
if (JSON.stringify(actual) !== JSON.stringify(options.rows)) throw new Error(formatTableDiff(table, options.columns, options.rows, actual));
|
|
925
|
-
return this;
|
|
1096
|
+
get stdout() {
|
|
1097
|
+
if (!this.commandResult) throw new Error(".stdout requires a CLI action (.exec())");
|
|
1098
|
+
return new StringAssertion(this.commandResult.stdout, "stdout", this.testDir);
|
|
926
1099
|
}
|
|
927
|
-
|
|
928
|
-
if (!
|
|
929
|
-
return this;
|
|
1100
|
+
get stderr() {
|
|
1101
|
+
if (!this.commandResult) throw new Error(".stderr requires a CLI action (.exec())");
|
|
1102
|
+
return new StringAssertion(this.commandResult.stderr, "stderr", this.testDir);
|
|
930
1103
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
return this;
|
|
1104
|
+
file(path) {
|
|
1105
|
+
return new FileAssertion(path, this.workDir ?? this.testDir);
|
|
934
1106
|
}
|
|
935
|
-
|
|
936
|
-
const
|
|
937
|
-
if (!
|
|
938
|
-
|
|
939
|
-
if (!actual.includes(content)) throw new Error(formatFileContentMismatch(path, content, actual));
|
|
940
|
-
return this;
|
|
1107
|
+
table(tableName, options) {
|
|
1108
|
+
const db = this.resolveDatabase(options?.service);
|
|
1109
|
+
if (!db) throw new Error(options?.service ? `table("${tableName}") requires database "${options.service}" but it was not found` : `table("${tableName}") requires a database adapter`);
|
|
1110
|
+
return new TableAssertion(tableName, db);
|
|
941
1111
|
}
|
|
942
1112
|
resolveDatabase(serviceName) {
|
|
943
1113
|
if (serviceName && this.config.databases) return this.config.databases.get(serviceName);
|
|
944
1114
|
return this.config.database;
|
|
945
1115
|
}
|
|
946
|
-
resolveWorkPath(path) {
|
|
947
|
-
if (this.workDir) return (0, node_path.resolve)(this.workDir, path);
|
|
948
|
-
return (0, node_path.resolve)(this.testDir, path);
|
|
949
|
-
}
|
|
950
1116
|
};
|
|
951
1117
|
var SpecificationBuilder = class {
|
|
952
1118
|
commandArgs = null;
|