@appthrust/kest 0.5.0 → 0.6.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 +57 -20
- package/package.json +1 -1
- package/ts/actions/assert-apply-error.ts +43 -0
- package/ts/actions/assert-create-error.ts +43 -0
- package/ts/actions/create-namespace.ts +1 -1
- package/ts/apis/index.ts +167 -0
- package/ts/naming/index.ts +2 -2
- package/ts/reporter/markdown/model.ts +1 -1
- package/ts/reporter/markdown/parser/index.ts +9 -5
- package/ts/reporter/markdown/renderer/index.ts +2 -3
- package/ts/scenario/index.ts +8 -0
package/README.md
CHANGED
|
@@ -113,17 +113,14 @@ await ns.assert({
|
|
|
113
113
|
Custom timeouts are supported per action:
|
|
114
114
|
|
|
115
115
|
```ts
|
|
116
|
-
await ns.assert(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
expect(this.status?.availableReplicas).toBe(3);
|
|
123
|
-
},
|
|
116
|
+
await ns.assert({
|
|
117
|
+
apiVersion: "apps/v1",
|
|
118
|
+
kind: "Deployment",
|
|
119
|
+
name: "my-app",
|
|
120
|
+
test() {
|
|
121
|
+
expect(this.status?.availableReplicas).toBe(3);
|
|
124
122
|
},
|
|
125
|
-
|
|
126
|
-
);
|
|
123
|
+
});
|
|
127
124
|
```
|
|
128
125
|
|
|
129
126
|
### Create Resources
|
|
@@ -252,7 +249,7 @@ await ns.assertOne<ConfigMap>({
|
|
|
252
249
|
test() {
|
|
253
250
|
expect(this.data?.mode).toBe("auto");
|
|
254
251
|
},
|
|
255
|
-
}
|
|
252
|
+
});
|
|
256
253
|
```
|
|
257
254
|
|
|
258
255
|
`assertOne` throws if zero or more than one resource matches, and retries until exactly one is found or the timeout expires.
|
|
@@ -269,19 +266,57 @@ await ns.assertAbsence({
|
|
|
269
266
|
});
|
|
270
267
|
```
|
|
271
268
|
|
|
272
|
-
|
|
269
|
+
### Error Assertions
|
|
270
|
+
|
|
271
|
+
Assert that applying or creating a resource produces an error (e.g. an admission webhook rejects the request, or a validation rule fails). The `test` callback inspects the error -- `this` is bound to the `Error`:
|
|
273
272
|
|
|
274
273
|
```ts
|
|
275
|
-
await ns.
|
|
276
|
-
{
|
|
277
|
-
apiVersion: "
|
|
278
|
-
kind: "
|
|
279
|
-
name: "my-
|
|
274
|
+
await ns.assertApplyError({
|
|
275
|
+
apply: {
|
|
276
|
+
apiVersion: "example.com/v1",
|
|
277
|
+
kind: "MyResource",
|
|
278
|
+
metadata: { name: "my-resource" },
|
|
279
|
+
spec: { immutableField: "changed" },
|
|
280
|
+
},
|
|
281
|
+
test() {
|
|
282
|
+
expect(this.message).toContain("field is immutable");
|
|
280
283
|
},
|
|
281
|
-
|
|
282
|
-
|
|
284
|
+
});
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The `test` callback participates in retry -- if it throws, the action is retried until the callback passes or the timeout expires. This is useful when a webhook is being set up asynchronously:
|
|
288
|
+
|
|
289
|
+
```ts
|
|
290
|
+
await ns.assertApplyError({
|
|
291
|
+
apply: {
|
|
292
|
+
apiVersion: "example.com/v1",
|
|
293
|
+
kind: "MyResource",
|
|
294
|
+
metadata: { name: "my-resource" },
|
|
295
|
+
spec: { immutableField: "changed" },
|
|
296
|
+
},
|
|
297
|
+
test(error) {
|
|
298
|
+
expect(error.message).toContain("field is immutable");
|
|
299
|
+
},
|
|
300
|
+
});
|
|
283
301
|
```
|
|
284
302
|
|
|
303
|
+
`assertCreateError` works identically for `kubectl create`:
|
|
304
|
+
|
|
305
|
+
```ts
|
|
306
|
+
await ns.assertCreateError({
|
|
307
|
+
create: {
|
|
308
|
+
apiVersion: "v1",
|
|
309
|
+
kind: "ConfigMap",
|
|
310
|
+
metadata: { name: "already-exists" },
|
|
311
|
+
},
|
|
312
|
+
test(error) {
|
|
313
|
+
expect(error.message).toContain("already exists");
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
If the apply/create unexpectedly succeeds (e.g. the webhook is not yet active), the resource is immediately reverted and the action retries until the expected error occurs.
|
|
319
|
+
|
|
285
320
|
### Label Resources
|
|
286
321
|
|
|
287
322
|
Add, update, or remove labels on Kubernetes resources using `kubectl label`:
|
|
@@ -470,6 +505,8 @@ The top-level API surface available in every test callback.
|
|
|
470
505
|
| ----------------------------------------------------------------------- | ----------------------------------------------------------- |
|
|
471
506
|
| `apply(manifest, options?)` | Apply a Kubernetes manifest and register cleanup |
|
|
472
507
|
| `create(manifest, options?)` | Create a Kubernetes resource and register cleanup |
|
|
508
|
+
| `assertApplyError(input, options?)` | Assert that `kubectl apply` produces an error |
|
|
509
|
+
| `assertCreateError(input, options?)` | Assert that `kubectl create` produces an error |
|
|
473
510
|
| `applyStatus(manifest, options?)` | Apply a status subresource (server-side apply) |
|
|
474
511
|
| `delete(resource, options?)` | Delete a resource by API version, kind, and name |
|
|
475
512
|
| `label(input, options?)` | Add, update, or remove labels on a resource |
|
|
@@ -486,7 +523,7 @@ The top-level API surface available in every test callback.
|
|
|
486
523
|
|
|
487
524
|
### Namespace / Cluster
|
|
488
525
|
|
|
489
|
-
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `create`, `applyStatus`, `delete`, `label`, `get`, `assert`, `assertAbsence`, `assertList`, `assertOne`) scoped to their namespace or cluster context. `Cluster` additionally supports `newNamespace`.
|
|
526
|
+
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `create`, `assertApplyError`, `assertCreateError`, `applyStatus`, `delete`, `label`, `get`, `assert`, `assertAbsence`, `assertList`, `assertOne`) scoped to their namespace or cluster context. `Cluster` additionally supports `newNamespace`.
|
|
490
527
|
|
|
491
528
|
`Namespace` also exposes a `name` property:
|
|
492
529
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AssertApplyErrorInput } from "../apis";
|
|
2
|
+
import { apply } from "./apply";
|
|
3
|
+
import type { MutateDef } from "./types";
|
|
4
|
+
|
|
5
|
+
export const assertApplyError = {
|
|
6
|
+
type: "mutate",
|
|
7
|
+
name: "AssertApplyError",
|
|
8
|
+
mutate:
|
|
9
|
+
({ kubectl }) =>
|
|
10
|
+
async (input) => {
|
|
11
|
+
const applyFn = apply.mutate({ kubectl });
|
|
12
|
+
let result: Awaited<ReturnType<typeof applyFn>> | undefined;
|
|
13
|
+
let rejection: Error | undefined;
|
|
14
|
+
try {
|
|
15
|
+
result = await applyFn(input.apply);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
rejection = err as Error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Apply succeeded unexpectedly -- revert immediately and throw so that
|
|
21
|
+
// the scenario wrapper retries.
|
|
22
|
+
if (result !== undefined) {
|
|
23
|
+
await result.revert();
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Expected ${apply.describe(input.apply)} to err, but it succeeded`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Apply erred as expected -- run test callback.
|
|
30
|
+
// biome-ignore lint/style/noNonNullAssertion: rejection is guaranteed non-undefined when result is undefined
|
|
31
|
+
await input.test.call(rejection!, rejection!);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
async revert() {
|
|
35
|
+
// Nothing to clean up -- the resource was never created.
|
|
36
|
+
},
|
|
37
|
+
output: undefined,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
describe: (input) => {
|
|
41
|
+
return `${apply.describe(input.apply)} (expected error)`;
|
|
42
|
+
},
|
|
43
|
+
} satisfies MutateDef<AssertApplyErrorInput, void>;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { AssertCreateErrorInput } from "../apis";
|
|
2
|
+
import { create } from "./create";
|
|
3
|
+
import type { MutateDef } from "./types";
|
|
4
|
+
|
|
5
|
+
export const assertCreateError = {
|
|
6
|
+
type: "mutate",
|
|
7
|
+
name: "AssertCreateError",
|
|
8
|
+
mutate:
|
|
9
|
+
({ kubectl }) =>
|
|
10
|
+
async (input) => {
|
|
11
|
+
const createFn = create.mutate({ kubectl });
|
|
12
|
+
let result: Awaited<ReturnType<typeof createFn>> | undefined;
|
|
13
|
+
let rejection: Error | undefined;
|
|
14
|
+
try {
|
|
15
|
+
result = await createFn(input.create);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
rejection = err as Error;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Create succeeded unexpectedly -- revert immediately and throw so that
|
|
21
|
+
// the scenario wrapper retries.
|
|
22
|
+
if (result !== undefined) {
|
|
23
|
+
await result.revert();
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Expected ${create.describe(input.create)} to err, but it succeeded`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Create erred as expected -- run test callback.
|
|
30
|
+
// biome-ignore lint/style/noNonNullAssertion: rejection is guaranteed non-undefined when result is undefined
|
|
31
|
+
await input.test.call(rejection!, rejection!);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
async revert() {
|
|
35
|
+
// Nothing to clean up -- the resource was never created.
|
|
36
|
+
},
|
|
37
|
+
output: undefined,
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
describe: (input) => {
|
|
41
|
+
return `${create.describe(input.create)} (expected error)`;
|
|
42
|
+
},
|
|
43
|
+
} satisfies MutateDef<AssertCreateErrorInput, void>;
|
package/ts/apis/index.ts
CHANGED
|
@@ -87,6 +87,75 @@ export interface Scenario {
|
|
|
87
87
|
options?: undefined | ActionOptions
|
|
88
88
|
): Promise<void>;
|
|
89
89
|
|
|
90
|
+
/**
|
|
91
|
+
* Asserts that `kubectl apply` produces an error.
|
|
92
|
+
*
|
|
93
|
+
* The manifest is applied, and the action succeeds when the API server
|
|
94
|
+
* returns an error (e.g. an admission webhook rejects the request). The
|
|
95
|
+
* `test` callback must also pass for the action to succeed.
|
|
96
|
+
*
|
|
97
|
+
* If the apply unexpectedly succeeds, the created resource is immediately
|
|
98
|
+
* reverted and the action is retried until the expected error occurs or the
|
|
99
|
+
* timeout expires.
|
|
100
|
+
*
|
|
101
|
+
* @template T - The expected Kubernetes resource shape.
|
|
102
|
+
* @param input - Manifest to apply and error assertion callback.
|
|
103
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* ```ts
|
|
107
|
+
* await s.assertApplyError({
|
|
108
|
+
* apply: {
|
|
109
|
+
* apiVersion: "example.com/v1",
|
|
110
|
+
* kind: "MyResource",
|
|
111
|
+
* metadata: { name: "my-resource" },
|
|
112
|
+
* spec: { immutableField: "changed" },
|
|
113
|
+
* },
|
|
114
|
+
* test() {
|
|
115
|
+
* expect(this.message).toContain("field is immutable");
|
|
116
|
+
* },
|
|
117
|
+
* });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
assertApplyError<T extends K8sResource>(
|
|
121
|
+
input: AssertApplyErrorInput<T>,
|
|
122
|
+
options?: undefined | ActionOptions
|
|
123
|
+
): Promise<void>;
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Asserts that `kubectl create` produces an error.
|
|
127
|
+
*
|
|
128
|
+
* The manifest is created, and the action succeeds when the API server
|
|
129
|
+
* returns an error. The `test` callback must also pass for the action to
|
|
130
|
+
* succeed.
|
|
131
|
+
*
|
|
132
|
+
* If the create unexpectedly succeeds, the created resource is immediately
|
|
133
|
+
* reverted and the action is retried until the expected error occurs or the
|
|
134
|
+
* timeout expires.
|
|
135
|
+
*
|
|
136
|
+
* @template T - The expected Kubernetes resource shape.
|
|
137
|
+
* @param input - Manifest to create and error assertion callback.
|
|
138
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* ```ts
|
|
142
|
+
* await s.assertCreateError({
|
|
143
|
+
* create: {
|
|
144
|
+
* apiVersion: "v1",
|
|
145
|
+
* kind: "ConfigMap",
|
|
146
|
+
* metadata: { name: "already-exists" },
|
|
147
|
+
* },
|
|
148
|
+
* test(error) {
|
|
149
|
+
* expect(error.message).toContain("already exists");
|
|
150
|
+
* },
|
|
151
|
+
* });
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
assertCreateError<T extends K8sResource>(
|
|
155
|
+
input: AssertCreateErrorInput<T>,
|
|
156
|
+
options?: undefined | ActionOptions
|
|
157
|
+
): Promise<void>;
|
|
158
|
+
|
|
90
159
|
/**
|
|
91
160
|
* Applies the `status` subresource using server-side apply.
|
|
92
161
|
*
|
|
@@ -573,6 +642,30 @@ export interface Cluster {
|
|
|
573
642
|
options?: undefined | ActionOptions
|
|
574
643
|
): Promise<void>;
|
|
575
644
|
|
|
645
|
+
/**
|
|
646
|
+
* Asserts that `kubectl apply` produces an error.
|
|
647
|
+
*
|
|
648
|
+
* @template T - The expected Kubernetes resource shape.
|
|
649
|
+
* @param input - Manifest to apply and error assertion callback.
|
|
650
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
651
|
+
*/
|
|
652
|
+
assertApplyError<T extends K8sResource>(
|
|
653
|
+
input: AssertApplyErrorInput<T>,
|
|
654
|
+
options?: undefined | ActionOptions
|
|
655
|
+
): Promise<void>;
|
|
656
|
+
|
|
657
|
+
/**
|
|
658
|
+
* Asserts that `kubectl create` produces an error.
|
|
659
|
+
*
|
|
660
|
+
* @template T - The expected Kubernetes resource shape.
|
|
661
|
+
* @param input - Manifest to create and error assertion callback.
|
|
662
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
663
|
+
*/
|
|
664
|
+
assertCreateError<T extends K8sResource>(
|
|
665
|
+
input: AssertCreateErrorInput<T>,
|
|
666
|
+
options?: undefined | ActionOptions
|
|
667
|
+
): Promise<void>;
|
|
668
|
+
|
|
576
669
|
/**
|
|
577
670
|
* Applies the `status` subresource using server-side apply.
|
|
578
671
|
*
|
|
@@ -871,6 +964,30 @@ export interface Namespace {
|
|
|
871
964
|
options?: undefined | ActionOptions
|
|
872
965
|
): Promise<void>;
|
|
873
966
|
|
|
967
|
+
/**
|
|
968
|
+
* Asserts that `kubectl apply` produces an error in this namespace.
|
|
969
|
+
*
|
|
970
|
+
* @template T - The expected Kubernetes resource shape.
|
|
971
|
+
* @param input - Manifest to apply and error assertion callback.
|
|
972
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
973
|
+
*/
|
|
974
|
+
assertApplyError<T extends K8sResource>(
|
|
975
|
+
input: AssertApplyErrorInput<T>,
|
|
976
|
+
options?: undefined | ActionOptions
|
|
977
|
+
): Promise<void>;
|
|
978
|
+
|
|
979
|
+
/**
|
|
980
|
+
* Asserts that `kubectl create` produces an error in this namespace.
|
|
981
|
+
*
|
|
982
|
+
* @template T - The expected Kubernetes resource shape.
|
|
983
|
+
* @param input - Manifest to create and error assertion callback.
|
|
984
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
985
|
+
*/
|
|
986
|
+
assertCreateError<T extends K8sResource>(
|
|
987
|
+
input: AssertCreateErrorInput<T>,
|
|
988
|
+
options?: undefined | ActionOptions
|
|
989
|
+
): Promise<void>;
|
|
990
|
+
|
|
874
991
|
/**
|
|
875
992
|
* Applies the `status` subresource in this namespace using server-side apply.
|
|
876
993
|
*
|
|
@@ -1279,6 +1396,56 @@ export interface ResourceOneTest<T extends K8sResource = K8sResource> {
|
|
|
1279
1396
|
readonly test: (this: T, resource: T) => unknown | Promise<unknown>;
|
|
1280
1397
|
}
|
|
1281
1398
|
|
|
1399
|
+
/**
|
|
1400
|
+
* A test definition for {@link Scenario.assertApplyError},
|
|
1401
|
+
* {@link Cluster.assertApplyError}, and {@link Namespace.assertApplyError}.
|
|
1402
|
+
*
|
|
1403
|
+
* Attempts `kubectl apply` and asserts that the API server returns an error.
|
|
1404
|
+
* When the operation errors as expected, the `test` callback is invoked with
|
|
1405
|
+
* `this` bound to the {@link Error}.
|
|
1406
|
+
*/
|
|
1407
|
+
export interface AssertApplyErrorInput<T extends K8sResource = K8sResource> {
|
|
1408
|
+
/**
|
|
1409
|
+
* The manifest to apply. Accepts the same formats as {@link Scenario.apply}:
|
|
1410
|
+
* an object literal, a YAML string, or an imported YAML module.
|
|
1411
|
+
*/
|
|
1412
|
+
readonly apply: ApplyingManifest<T>;
|
|
1413
|
+
|
|
1414
|
+
/**
|
|
1415
|
+
* Assertion callback invoked when the apply errors as expected.
|
|
1416
|
+
*
|
|
1417
|
+
* `this` is bound to the {@link Error} returned by the API server.
|
|
1418
|
+
* Throwing (or rejecting) signals that the error did not match expectations
|
|
1419
|
+
* and triggers a retry (if timeout allows).
|
|
1420
|
+
*/
|
|
1421
|
+
readonly test: (this: Error, error: Error) => unknown | Promise<unknown>;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
/**
|
|
1425
|
+
* A test definition for {@link Scenario.assertCreateError},
|
|
1426
|
+
* {@link Cluster.assertCreateError}, and {@link Namespace.assertCreateError}.
|
|
1427
|
+
*
|
|
1428
|
+
* Attempts `kubectl create` and asserts that the API server returns an error.
|
|
1429
|
+
* When the operation errors as expected, the `test` callback is invoked with
|
|
1430
|
+
* `this` bound to the {@link Error}.
|
|
1431
|
+
*/
|
|
1432
|
+
export interface AssertCreateErrorInput<T extends K8sResource = K8sResource> {
|
|
1433
|
+
/**
|
|
1434
|
+
* The manifest to create. Accepts the same formats as {@link Scenario.create}:
|
|
1435
|
+
* an object literal, a YAML string, or an imported YAML module.
|
|
1436
|
+
*/
|
|
1437
|
+
readonly create: ApplyingManifest<T>;
|
|
1438
|
+
|
|
1439
|
+
/**
|
|
1440
|
+
* Assertion callback invoked when the create errors as expected.
|
|
1441
|
+
*
|
|
1442
|
+
* `this` is bound to the {@link Error} returned by the API server.
|
|
1443
|
+
* Throwing (or rejecting) signals that the error did not match expectations
|
|
1444
|
+
* and triggers a retry (if timeout allows).
|
|
1445
|
+
*/
|
|
1446
|
+
readonly test: (this: Error, error: Error) => unknown | Promise<unknown>;
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1282
1449
|
/**
|
|
1283
1450
|
* Kubernetes cluster selector for {@link Scenario.useCluster}.
|
|
1284
1451
|
*/
|
package/ts/naming/index.ts
CHANGED
|
@@ -8,7 +8,8 @@ const consonantDigits = "bcdfghjklmnpqrstvwxyz0123456789";
|
|
|
8
8
|
export function randomConsonantDigits(length = 8): string {
|
|
9
9
|
let result = "";
|
|
10
10
|
for (let i = 0; i < length; i++) {
|
|
11
|
-
result +=
|
|
11
|
+
result +=
|
|
12
|
+
consonantDigits[Math.floor(Math.random() * consonantDigits.length)];
|
|
12
13
|
}
|
|
13
14
|
return result;
|
|
14
15
|
}
|
|
@@ -24,4 +25,3 @@ export function randomConsonantDigits(length = 8): string {
|
|
|
24
25
|
export function generateName(prefix: string, suffixLength = 5): string {
|
|
25
26
|
return `${prefix}${randomConsonantDigits(suffixLength)}`;
|
|
26
27
|
}
|
|
27
|
-
|
|
@@ -114,7 +114,7 @@ function handleActionStart(
|
|
|
114
114
|
return;
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
-
const action: Action = { name: event.data.description };
|
|
117
|
+
const action: Action = { name: event.data.description, commands: [] };
|
|
118
118
|
scenario.overview.push({
|
|
119
119
|
name: event.data.description,
|
|
120
120
|
status: "pending",
|
|
@@ -180,17 +180,21 @@ function handleCommandResult(
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
const { currentAction } = state;
|
|
183
|
-
if (!currentAction
|
|
183
|
+
if (!currentAction || currentAction.commands.length === 0) {
|
|
184
184
|
return;
|
|
185
185
|
}
|
|
186
186
|
|
|
187
|
-
|
|
187
|
+
const command = currentAction.commands.at(-1);
|
|
188
|
+
if (!command) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
command.stdout = {
|
|
188
192
|
text: event.data.stdout,
|
|
189
193
|
...(event.data.stdoutLanguage
|
|
190
194
|
? { language: event.data.stdoutLanguage }
|
|
191
195
|
: {}),
|
|
192
196
|
};
|
|
193
|
-
|
|
197
|
+
command.stderr = {
|
|
194
198
|
text: event.data.stderr,
|
|
195
199
|
...(event.data.stderrLanguage
|
|
196
200
|
? { language: event.data.stderrLanguage }
|
|
@@ -216,7 +220,7 @@ function handleCommandRun(
|
|
|
216
220
|
if (!state.currentAction) {
|
|
217
221
|
return;
|
|
218
222
|
}
|
|
219
|
-
state.currentAction.
|
|
223
|
+
state.currentAction.commands.push(createCommandFromRun(event));
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
function handleScenarioStart(
|
|
@@ -165,7 +165,7 @@ export function renderReport(
|
|
|
165
165
|
if (!status) {
|
|
166
166
|
if (action.error) {
|
|
167
167
|
status = "failure";
|
|
168
|
-
} else if (action.
|
|
168
|
+
} else if (action.commands.length > 0) {
|
|
169
169
|
status = "success";
|
|
170
170
|
} else {
|
|
171
171
|
status = "pending";
|
|
@@ -180,8 +180,7 @@ export function renderReport(
|
|
|
180
180
|
lines.push(`**${emoji} ${stripAnsi(action.name)}**${attemptsSuffix}`);
|
|
181
181
|
lines.push("");
|
|
182
182
|
|
|
183
|
-
const cmd
|
|
184
|
-
if (cmd) {
|
|
183
|
+
for (const cmd of action.commands) {
|
|
185
184
|
const base = [cmd.cmd, ...cmd.args].join(" ").trim();
|
|
186
185
|
const stdin = cmd.stdin?.text;
|
|
187
186
|
const stdinLanguage = cmd.stdin?.language ?? "text";
|
package/ts/scenario/index.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { apply } from "../actions/apply";
|
|
|
2
2
|
import { applyStatus } from "../actions/apply-status";
|
|
3
3
|
import { assert } from "../actions/assert";
|
|
4
4
|
import { assertAbsence } from "../actions/assert-absence";
|
|
5
|
+
import { assertApplyError } from "../actions/assert-apply-error";
|
|
6
|
+
import { assertCreateError } from "../actions/assert-create-error";
|
|
5
7
|
import { assertList } from "../actions/assert-list";
|
|
6
8
|
import { assertOne } from "../actions/assert-one";
|
|
7
9
|
import { create } from "../actions/create";
|
|
@@ -39,6 +41,8 @@ export function createScenario(deps: CreateScenarioOptions): InternalScenario {
|
|
|
39
41
|
return {
|
|
40
42
|
apply: createMutateFn(deps, apply),
|
|
41
43
|
create: createMutateFn(deps, create),
|
|
44
|
+
assertApplyError: createMutateFn(deps, assertApplyError),
|
|
45
|
+
assertCreateError: createMutateFn(deps, assertCreateError),
|
|
42
46
|
applyStatus: createOneWayMutateFn(deps, applyStatus),
|
|
43
47
|
delete: createOneWayMutateFn(deps, deleteResource),
|
|
44
48
|
label: createOneWayMutateFn(deps, label),
|
|
@@ -205,6 +209,8 @@ const createNewNamespaceFn =
|
|
|
205
209
|
name: namespaceName,
|
|
206
210
|
apply: createMutateFn(namespacedDeps, apply),
|
|
207
211
|
create: createMutateFn(namespacedDeps, create),
|
|
212
|
+
assertApplyError: createMutateFn(namespacedDeps, assertApplyError),
|
|
213
|
+
assertCreateError: createMutateFn(namespacedDeps, assertCreateError),
|
|
208
214
|
applyStatus: createOneWayMutateFn(namespacedDeps, applyStatus),
|
|
209
215
|
delete: createOneWayMutateFn(namespacedDeps, deleteResource),
|
|
210
216
|
label: createOneWayMutateFn(namespacedDeps, label),
|
|
@@ -229,6 +235,8 @@ const createUseClusterFn =
|
|
|
229
235
|
return {
|
|
230
236
|
apply: createMutateFn(clusterDeps, apply),
|
|
231
237
|
create: createMutateFn(clusterDeps, create),
|
|
238
|
+
assertApplyError: createMutateFn(clusterDeps, assertApplyError),
|
|
239
|
+
assertCreateError: createMutateFn(clusterDeps, assertCreateError),
|
|
232
240
|
applyStatus: createOneWayMutateFn(clusterDeps, applyStatus),
|
|
233
241
|
delete: createOneWayMutateFn(clusterDeps, deleteResource),
|
|
234
242
|
label: createOneWayMutateFn(clusterDeps, label),
|