@appthrust/kest 0.1.1 → 0.2.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 +6 -3
- package/package.json +2 -2
- package/ts/actions/delete.ts +25 -0
- package/ts/apis/index.ts +59 -1
- package/ts/scenario/index.ts +4 -0
package/README.md
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
# Kest
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
3
5
|
> **Preview Release** -- Kest is currently in `0.x` preview. The API may change based on feedback. Breaking changes can occur in any `0.x` release. A stable `1.0.0` will be released once the API solidifies. Feel free to [open an issue](https://github.com/appthrust/kest/issues/new) if you have any feedback.
|
|
4
6
|
|
|
5
|
-
**
|
|
7
|
+
**Kubernetes E2E testing designed for humans and AI alike**
|
|
6
8
|
|
|
7
|
-
Kest makes it easy to write reliable end-to-end tests for Kubernetes controllers, operators, and admission webhooks. You write test scenarios in TypeScript with full type safety, autocompletion, and the familiar `expect()` API.
|
|
9
|
+
Kest makes it easy to write reliable end-to-end tests for Kubernetes controllers, operators, and admission webhooks. You write test scenarios in TypeScript with full type safety, autocompletion, and the familiar `expect()` API. When a test fails, Kest generates structured Markdown reports that are easy for humans to scan and for AI assistants to parse -- making troubleshooting straightforward regardless of who (or what) is debugging.
|
|
8
10
|
|
|
9
11
|
```ts
|
|
10
12
|
import { expect } from "bun:test";
|
|
@@ -335,6 +337,7 @@ The top-level API surface available in every test callback.
|
|
|
335
337
|
| ----------------------------------------------------------------------- | ------------------------------------------------ |
|
|
336
338
|
| `apply(manifest, options?)` | Apply a Kubernetes manifest and register cleanup |
|
|
337
339
|
| `applyStatus(manifest, options?)` | Apply a status subresource (server-side apply) |
|
|
340
|
+
| `delete(resource, options?)` | Delete a resource by API version, kind, and name |
|
|
338
341
|
| `get(resource, options?)` | Fetch a resource by API version, kind, and name |
|
|
339
342
|
| `assert(resource, options?)` | Fetch a resource and run assertions with retries |
|
|
340
343
|
| `assertList(resource, options?)` | Fetch a list of resources and run assertions |
|
|
@@ -345,7 +348,7 @@ The top-level API surface available in every test callback.
|
|
|
345
348
|
|
|
346
349
|
### Namespace / Cluster
|
|
347
350
|
|
|
348
|
-
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `applyStatus`, `get`, `assert`, `assertList
|
|
351
|
+
Returned by `newNamespace()` and `useCluster()` respectively. They expose the same core methods (`apply`, `applyStatus`, `delete`, `get`, `assert`, `assertList`) scoped to their namespace or cluster context. `Cluster` additionally supports `newNamespace`.
|
|
349
352
|
|
|
350
353
|
### Action Options
|
|
351
354
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appthrust/kest",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Kubernetes E2E testing framework designed for humans and AI alike",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "ts/index.ts",
|
|
7
7
|
"exports": {
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { K8sResource, K8sResourceReference } from "../apis";
|
|
2
|
+
import type { OneWayMutateDef } from "./types";
|
|
3
|
+
|
|
4
|
+
export const deleteResource = {
|
|
5
|
+
type: "oneWayMutate",
|
|
6
|
+
name: "Delete",
|
|
7
|
+
mutate:
|
|
8
|
+
({ kubectl }) =>
|
|
9
|
+
async <T extends K8sResource>(resource: K8sResourceReference<T>) => {
|
|
10
|
+
await kubectl.delete(toKubectlType(resource), resource.name);
|
|
11
|
+
return undefined;
|
|
12
|
+
},
|
|
13
|
+
} satisfies OneWayMutateDef<K8sResourceReference, void>;
|
|
14
|
+
|
|
15
|
+
function toKubectlType<T extends K8sResource>(
|
|
16
|
+
resource: K8sResourceReference<T>
|
|
17
|
+
): string {
|
|
18
|
+
const { kind, apiVersion } = resource;
|
|
19
|
+
const [group, version] = apiVersion.split("/");
|
|
20
|
+
if (version === undefined) {
|
|
21
|
+
// core group cannot include version in the type
|
|
22
|
+
return kind;
|
|
23
|
+
}
|
|
24
|
+
return [kind, version, group].filter(Boolean).join(".");
|
|
25
|
+
}
|
package/ts/apis/index.ts
CHANGED
|
@@ -13,7 +13,8 @@ import type { $ as BunDollar } from "bun";
|
|
|
13
13
|
* Some actions also register cleanup ("revert") handlers which run during
|
|
14
14
|
* scenario cleanup. For example, {@link Scenario.apply} registers a revert that
|
|
15
15
|
* deletes the applied resource. One-way mutations such as
|
|
16
|
-
* {@link Scenario.applyStatus} do not register a
|
|
16
|
+
* {@link Scenario.applyStatus} and {@link Scenario.delete} do not register a
|
|
17
|
+
* revert.
|
|
17
18
|
*/
|
|
18
19
|
|
|
19
20
|
/**
|
|
@@ -88,6 +89,31 @@ export interface Scenario {
|
|
|
88
89
|
options?: undefined | ActionOptions
|
|
89
90
|
): Promise<void>;
|
|
90
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Deletes a Kubernetes resource using `kubectl delete`.
|
|
94
|
+
*
|
|
95
|
+
* This action is retried when it throws.
|
|
96
|
+
*
|
|
97
|
+
* Note: this is a one-way mutation and does not register a cleanup handler.
|
|
98
|
+
*
|
|
99
|
+
* @template T - The expected Kubernetes resource shape.
|
|
100
|
+
* @param resource - Group/version/kind and name of the resource to delete.
|
|
101
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* await s.delete({
|
|
106
|
+
* apiVersion: "v1",
|
|
107
|
+
* kind: "ConfigMap",
|
|
108
|
+
* name: "my-config",
|
|
109
|
+
* });
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
delete<T extends K8sResource>(
|
|
113
|
+
resource: K8sResourceReference<T>,
|
|
114
|
+
options?: undefined | ActionOptions
|
|
115
|
+
): Promise<void>;
|
|
116
|
+
|
|
91
117
|
/**
|
|
92
118
|
* Fetches a Kubernetes resource and returns it as a typed object.
|
|
93
119
|
*
|
|
@@ -370,6 +396,22 @@ export interface Cluster {
|
|
|
370
396
|
options?: undefined | ActionOptions
|
|
371
397
|
): Promise<void>;
|
|
372
398
|
|
|
399
|
+
/**
|
|
400
|
+
* Deletes a Kubernetes resource in this cluster using `kubectl delete`.
|
|
401
|
+
*
|
|
402
|
+
* This action is retried when it throws.
|
|
403
|
+
*
|
|
404
|
+
* Note: this is a one-way mutation and does not register a cleanup handler.
|
|
405
|
+
*
|
|
406
|
+
* @template T - The expected Kubernetes resource shape.
|
|
407
|
+
* @param resource - Group/version/kind and name of the resource to delete.
|
|
408
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
409
|
+
*/
|
|
410
|
+
delete<T extends K8sResource>(
|
|
411
|
+
resource: K8sResourceReference<T>,
|
|
412
|
+
options?: undefined | ActionOptions
|
|
413
|
+
): Promise<void>;
|
|
414
|
+
|
|
373
415
|
/**
|
|
374
416
|
* Fetches a Kubernetes resource by GVK and name.
|
|
375
417
|
*
|
|
@@ -534,6 +576,22 @@ export interface Namespace {
|
|
|
534
576
|
options?: undefined | ActionOptions
|
|
535
577
|
): Promise<void>;
|
|
536
578
|
|
|
579
|
+
/**
|
|
580
|
+
* Deletes a Kubernetes resource in this namespace using `kubectl delete`.
|
|
581
|
+
*
|
|
582
|
+
* This action is retried when it throws.
|
|
583
|
+
*
|
|
584
|
+
* Note: this is a one-way mutation and does not register a cleanup handler.
|
|
585
|
+
*
|
|
586
|
+
* @template T - The expected Kubernetes resource shape.
|
|
587
|
+
* @param resource - Group/version/kind and name of the resource to delete.
|
|
588
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
589
|
+
*/
|
|
590
|
+
delete<T extends K8sResource>(
|
|
591
|
+
resource: K8sResourceReference<T>,
|
|
592
|
+
options?: undefined | ActionOptions
|
|
593
|
+
): Promise<void>;
|
|
594
|
+
|
|
537
595
|
/**
|
|
538
596
|
* Fetches a namespaced Kubernetes resource by GVK and name.
|
|
539
597
|
*
|
package/ts/scenario/index.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { applyNamespace } from "../actions/apply-namespace";
|
|
|
3
3
|
import { applyStatus } from "../actions/apply-status";
|
|
4
4
|
import { assert } from "../actions/assert";
|
|
5
5
|
import { assertList } from "../actions/assert-list";
|
|
6
|
+
import { deleteResource } from "../actions/delete";
|
|
6
7
|
import { exec } from "../actions/exec";
|
|
7
8
|
import { get } from "../actions/get";
|
|
8
9
|
import type { MutateDef, OneWayMutateDef, QueryDef } from "../actions/types";
|
|
@@ -31,6 +32,7 @@ export function createScenario(deps: CreateScenarioOptions): InternalScenario {
|
|
|
31
32
|
return {
|
|
32
33
|
apply: createMutateFn(deps, apply),
|
|
33
34
|
applyStatus: createOneWayMutateFn(deps, applyStatus),
|
|
35
|
+
delete: createOneWayMutateFn(deps, deleteResource),
|
|
34
36
|
exec: createMutateFn(deps, exec),
|
|
35
37
|
get: createQueryFn(deps, get),
|
|
36
38
|
assert: createQueryFn(deps, assert),
|
|
@@ -193,6 +195,7 @@ const createNewNamespaceFn =
|
|
|
193
195
|
return {
|
|
194
196
|
apply: createMutateFn(namespacedDeps, apply),
|
|
195
197
|
applyStatus: createOneWayMutateFn(namespacedDeps, applyStatus),
|
|
198
|
+
delete: createOneWayMutateFn(namespacedDeps, deleteResource),
|
|
196
199
|
get: createQueryFn(namespacedDeps, get),
|
|
197
200
|
assert: createQueryFn(namespacedDeps, assert),
|
|
198
201
|
assertList: createQueryFn(namespacedDeps, assertList),
|
|
@@ -212,6 +215,7 @@ const createUseClusterFn =
|
|
|
212
215
|
return {
|
|
213
216
|
apply: createMutateFn(clusterDeps, apply),
|
|
214
217
|
applyStatus: createOneWayMutateFn(clusterDeps, applyStatus),
|
|
218
|
+
delete: createOneWayMutateFn(clusterDeps, deleteResource),
|
|
215
219
|
get: createQueryFn(clusterDeps, get),
|
|
216
220
|
assert: createQueryFn(clusterDeps, assert),
|
|
217
221
|
assertList: createQueryFn(clusterDeps, assertList),
|