@appthrust/kest 0.11.0 → 0.13.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 +37 -4
- package/biome/recommended.json +10 -0
- package/package.json +4 -2
- package/ts/recording/index.ts +2 -1
- package/ts/reporter/markdown/model.ts +1 -0
- package/ts/reporter/markdown/parser/index.ts +5 -0
- package/ts/reporter/markdown/renderer/index.ts +15 -2
- package/ts/reverting/index.ts +3 -0
- package/ts/scenario/index.ts +7 -3
- package/ts/test.ts +4 -1
package/README.md
CHANGED
|
@@ -1168,12 +1168,45 @@ await ns.assert<MyCustomResource>({
|
|
|
1168
1168
|
});
|
|
1169
1169
|
```
|
|
1170
1170
|
|
|
1171
|
+
## Tips
|
|
1172
|
+
|
|
1173
|
+
### Debugging failed tests
|
|
1174
|
+
|
|
1175
|
+
By default, Kest cleans up all resources after every test -- even when the test fails. This keeps the cluster tidy but destroys the state you need for debugging. To preserve resources on failure, set `KEST_PRESERVE_ON_FAILURE=1`:
|
|
1176
|
+
|
|
1177
|
+
```sh
|
|
1178
|
+
KEST_PRESERVE_ON_FAILURE=1 bun test
|
|
1179
|
+
```
|
|
1180
|
+
|
|
1181
|
+
When active, Kest skips cleanup for failed scenarios so you can inspect the cluster with `kubectl`. The test report will show a "Cleanup (skipped)" notice instead of the usual cleanup table. Remember to delete the leftover resources manually once you're done investigating.
|
|
1182
|
+
|
|
1183
|
+
### Biome configuration
|
|
1184
|
+
|
|
1185
|
+
If you use [Biome](https://biomejs.dev/) for linting, the `noDoneCallback` rule may flag kest's `test` callback parameter as a false positive. The parameter is a `Scenario` object, not a done callback.
|
|
1186
|
+
|
|
1187
|
+
To suppress this, extend kest's shareable Biome config in your `biome.json`:
|
|
1188
|
+
|
|
1189
|
+
```json
|
|
1190
|
+
{
|
|
1191
|
+
"extends": ["@appthrust/kest/biome/recommended.json"]
|
|
1192
|
+
}
|
|
1193
|
+
```
|
|
1194
|
+
|
|
1195
|
+
This disables `lint/style/noDoneCallback` while leaving all other rules untouched. If you already extend another config, add kest's config after it:
|
|
1196
|
+
|
|
1197
|
+
```json
|
|
1198
|
+
{
|
|
1199
|
+
"extends": ["@suin/biome.json", "@appthrust/kest/biome/recommended.json"]
|
|
1200
|
+
}
|
|
1201
|
+
```
|
|
1202
|
+
|
|
1171
1203
|
## Environment Variables
|
|
1172
1204
|
|
|
1173
|
-
| Variable
|
|
1174
|
-
|
|
|
1175
|
-
| `KEST_SHOW_REPORT`
|
|
1176
|
-
| `KEST_SHOW_EVENTS`
|
|
1205
|
+
| Variable | Description |
|
|
1206
|
+
| --------------------------- | ----------------------------------------------------------------------- |
|
|
1207
|
+
| `KEST_SHOW_REPORT` | Set to `"1"` to show Markdown reports for all tests (not just failures) |
|
|
1208
|
+
| `KEST_SHOW_EVENTS` | Set to `"1"` to dump raw recorder events for debugging |
|
|
1209
|
+
| `KEST_PRESERVE_ON_FAILURE` | Set to `"1"` to skip cleanup when a test fails, preserving cluster state for debugging |
|
|
1177
1210
|
|
|
1178
1211
|
## License
|
|
1179
1212
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appthrust/kest",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Kubernetes E2E testing framework designed for humans and AI alike",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "ts/index.ts",
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./ts/index.ts",
|
|
10
10
|
"default": "./ts/index.ts"
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
|
+
"./biome/recommended.json": "./biome/recommended.json"
|
|
12
13
|
},
|
|
13
14
|
"files": [
|
|
14
15
|
"ts/**/*.ts",
|
|
15
16
|
"!ts/**/*.test.ts",
|
|
17
|
+
"biome/",
|
|
16
18
|
"example/",
|
|
17
19
|
"LICENSE",
|
|
18
20
|
"README.md"
|
package/ts/recording/index.ts
CHANGED
|
@@ -71,7 +71,8 @@ type RetryEvent =
|
|
|
71
71
|
|
|
72
72
|
type RevertingsEvent =
|
|
73
73
|
| BaseEvent<"RevertingsStart">
|
|
74
|
-
| BaseEvent<"RevertingsEnd"
|
|
74
|
+
| BaseEvent<"RevertingsEnd">
|
|
75
|
+
| BaseEvent<"RevertingsSkipped">;
|
|
75
76
|
|
|
76
77
|
type BDDEvent =
|
|
77
78
|
| BaseEvent<"BDDGiven", { readonly description: string }>
|
|
@@ -7,6 +7,7 @@ export interface Scenario {
|
|
|
7
7
|
overview: Array<OverviewItem>;
|
|
8
8
|
details: Array<Tagged<"BDDSection", BDDSection> | Tagged<"Action", Action>>;
|
|
9
9
|
cleanup: Array<CleanupItem>;
|
|
10
|
+
cleanupSkipped?: boolean;
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
type Tagged<Tag extends string, Target extends object> = Target & {
|
|
@@ -75,6 +75,11 @@ function handleNonBDDEvent(state: ParseState, event: Event): void {
|
|
|
75
75
|
state.inCleanup = false;
|
|
76
76
|
clearCurrentActionState(state);
|
|
77
77
|
return;
|
|
78
|
+
case "RevertingsSkipped": {
|
|
79
|
+
const scenario = ensureScenario(state.currentScenario, state.report);
|
|
80
|
+
scenario.cleanupSkipped = true;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
78
83
|
case "ActionStart":
|
|
79
84
|
handleActionStart(state, event);
|
|
80
85
|
return;
|
|
@@ -157,7 +157,8 @@ export async function renderReport(
|
|
|
157
157
|
const isEmpty =
|
|
158
158
|
scenario.overview.length === 0 &&
|
|
159
159
|
scenario.details.length === 0 &&
|
|
160
|
-
scenario.cleanup.length === 0
|
|
160
|
+
scenario.cleanup.length === 0 &&
|
|
161
|
+
!scenario.cleanupSkipped;
|
|
161
162
|
if (isEmpty) {
|
|
162
163
|
continue;
|
|
163
164
|
}
|
|
@@ -294,7 +295,19 @@ export async function renderReport(
|
|
|
294
295
|
}
|
|
295
296
|
|
|
296
297
|
// Cleanup
|
|
297
|
-
if (scenario.
|
|
298
|
+
if (scenario.cleanupSkipped) {
|
|
299
|
+
lines.push("### Cleanup (skipped)");
|
|
300
|
+
lines.push("");
|
|
301
|
+
lines.push(
|
|
302
|
+
"Cleanup was skipped because `KEST_PRESERVE_ON_FAILURE=1` is set."
|
|
303
|
+
);
|
|
304
|
+
lines.push(
|
|
305
|
+
"Resources created during this scenario were **not** deleted."
|
|
306
|
+
);
|
|
307
|
+
lines.push(
|
|
308
|
+
"To clean up manually, run the revert commands from a passing test run."
|
|
309
|
+
);
|
|
310
|
+
} else if (scenario.cleanup.length > 0) {
|
|
298
311
|
lines.push("### Cleanup");
|
|
299
312
|
lines.push("");
|
|
300
313
|
lines.push("| # | Action | Status |");
|
package/ts/reverting/index.ts
CHANGED
package/ts/scenario/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ import { retryUntil } from "../retry";
|
|
|
32
32
|
import type { Reverting } from "../reverting";
|
|
33
33
|
|
|
34
34
|
export interface InternalScenario extends Scenario {
|
|
35
|
-
cleanup(): Promise<void>;
|
|
35
|
+
cleanup(options?: { skip?: boolean }): Promise<void>;
|
|
36
36
|
getReport(): Promise<string>;
|
|
37
37
|
}
|
|
38
38
|
|
|
@@ -61,8 +61,12 @@ export function createScenario(deps: CreateScenarioOptions): InternalScenario {
|
|
|
61
61
|
generateName: (prefix: string) => generateRandomName(prefix),
|
|
62
62
|
newNamespace: createNewNamespaceFn(deps),
|
|
63
63
|
useCluster: createUseClusterFn(deps),
|
|
64
|
-
async cleanup() {
|
|
65
|
-
|
|
64
|
+
async cleanup(options?: { skip?: boolean }) {
|
|
65
|
+
if (options?.skip) {
|
|
66
|
+
reverting.skip();
|
|
67
|
+
} else {
|
|
68
|
+
await reverting.revert();
|
|
69
|
+
}
|
|
66
70
|
},
|
|
67
71
|
async getReport() {
|
|
68
72
|
return await reporter.report(recorder.getEvents());
|
package/ts/test.ts
CHANGED
|
@@ -43,6 +43,7 @@ type BunTestRunner = (
|
|
|
43
43
|
const workspaceRoot = await getWorkspaceRoot();
|
|
44
44
|
const showReport = process.env["KEST_SHOW_REPORT"] === "1";
|
|
45
45
|
const showEvents = process.env["KEST_SHOW_EVENTS"] === "1";
|
|
46
|
+
const preserveOnFailure = process.env["KEST_PRESERVE_ON_FAILURE"] === "1";
|
|
46
47
|
|
|
47
48
|
function makeScenarioTest(runner: BunTestRunner): TestFunction {
|
|
48
49
|
return (label, fn, options) => {
|
|
@@ -66,7 +67,9 @@ function makeScenarioTest(runner: BunTestRunner): TestFunction {
|
|
|
66
67
|
} catch (error) {
|
|
67
68
|
testErr = error as Error;
|
|
68
69
|
}
|
|
69
|
-
await scenario.cleanup(
|
|
70
|
+
await scenario.cleanup({
|
|
71
|
+
skip: preserveOnFailure && testErr !== undefined,
|
|
72
|
+
});
|
|
70
73
|
recorder.record("ScenarioEnd", {});
|
|
71
74
|
await report(recorder, scenario, testErr);
|
|
72
75
|
if (testErr) {
|