@appthrust/kest 0.1.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/LICENSE +21 -0
- package/README.md +412 -0
- package/example/config-map.yaml +6 -0
- package/example/example-report.md +744 -0
- package/example/example.test.ts +225 -0
- package/example/hello-world-crd.yaml +145 -0
- package/package.json +62 -0
- package/ts/actions/apply-namespace.ts +29 -0
- package/ts/actions/apply-status.ts +23 -0
- package/ts/actions/apply.ts +25 -0
- package/ts/actions/assert-list.ts +63 -0
- package/ts/actions/assert.ts +34 -0
- package/ts/actions/exec.ts +21 -0
- package/ts/actions/get.ts +40 -0
- package/ts/actions/types.ts +48 -0
- package/ts/apis/index.ts +788 -0
- package/ts/bdd/index.ts +30 -0
- package/ts/duration/index.ts +171 -0
- package/ts/index.ts +3 -0
- package/ts/k8s-resource/index.ts +120 -0
- package/ts/kubectl/index.ts +351 -0
- package/ts/recording/index.ts +134 -0
- package/ts/reporter/index.ts +0 -0
- package/ts/reporter/interface.ts +5 -0
- package/ts/reporter/markdown.ts +962 -0
- package/ts/retry.ts +112 -0
- package/ts/reverting/index.ts +36 -0
- package/ts/scenario/index.ts +220 -0
- package/ts/test.ts +127 -0
- package/ts/workspace/find-up.ts +20 -0
- package/ts/workspace/index.ts +25 -0
- package/ts/yaml/index.ts +14 -0
package/ts/apis/index.ts
ADDED
|
@@ -0,0 +1,788 @@
|
|
|
1
|
+
import type { $ as BunDollar } from "bun";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Kest public APIs.
|
|
5
|
+
*
|
|
6
|
+
* This module defines the TypeScript types for Kest's scenario DSL.
|
|
7
|
+
*
|
|
8
|
+
* A {@link Scenario} represents a single run (usually a single test) and exposes
|
|
9
|
+
* high-level actions for interacting with Kubernetes. Most actions are executed
|
|
10
|
+
* with built-in retries: when an action throws, Kest retries it until it
|
|
11
|
+
* succeeds or {@link ActionOptions.timeout} elapses.
|
|
12
|
+
*
|
|
13
|
+
* Some actions also register cleanup ("revert") handlers which run during
|
|
14
|
+
* scenario cleanup. For example, {@link Scenario.apply} registers a revert that
|
|
15
|
+
* deletes the applied resource. One-way mutations such as
|
|
16
|
+
* {@link Scenario.applyStatus} do not register a revert.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Scenario-level DSL.
|
|
21
|
+
*
|
|
22
|
+
* A scenario is the top-level entrypoint for interacting with Kubernetes during
|
|
23
|
+
* a test run.
|
|
24
|
+
*/
|
|
25
|
+
export interface Scenario {
|
|
26
|
+
// Basic actions
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Applies a Kubernetes manifest with `kubectl apply`.
|
|
30
|
+
*
|
|
31
|
+
* The manifest is validated and then applied. When the action succeeds, Kest
|
|
32
|
+
* registers a cleanup handler that deletes the resource using
|
|
33
|
+
* `kubectl delete <kind>/<metadata.name>` during scenario cleanup.
|
|
34
|
+
*
|
|
35
|
+
* This action is retried when it throws.
|
|
36
|
+
*
|
|
37
|
+
* @template T - The expected Kubernetes resource shape.
|
|
38
|
+
* @param manifest - YAML string, resource object, or imported YAML module.
|
|
39
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* await s.apply({
|
|
44
|
+
* apiVersion: "v1",
|
|
45
|
+
* kind: "ConfigMap",
|
|
46
|
+
* metadata: { name: "my-config" },
|
|
47
|
+
* data: { mode: "demo" },
|
|
48
|
+
* });
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
apply<T extends K8sResource>(
|
|
52
|
+
manifest: ApplyingManifest<T>,
|
|
53
|
+
options?: undefined | ActionOptions
|
|
54
|
+
): Promise<void>;
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Applies the `status` subresource using server-side apply.
|
|
58
|
+
*
|
|
59
|
+
* Internally, this uses:
|
|
60
|
+
* `kubectl apply --server-side --subresource=status ...`
|
|
61
|
+
*
|
|
62
|
+
* The provided manifest must include `status`. This is useful for tests that
|
|
63
|
+
* need to simulate controllers by manually setting conditions/fields in
|
|
64
|
+
* `status`.
|
|
65
|
+
*
|
|
66
|
+
* This action is retried when it throws.
|
|
67
|
+
*
|
|
68
|
+
* Note: this is a one-way mutation and does not register a cleanup handler.
|
|
69
|
+
*
|
|
70
|
+
* @template T - The expected Kubernetes resource shape.
|
|
71
|
+
* @param manifest - Resource object that includes a `status` field.
|
|
72
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* await s.applyStatus({
|
|
77
|
+
* apiVersion: "example.com/v1",
|
|
78
|
+
* kind: "HelloWorld",
|
|
79
|
+
* metadata: { name: "my-hello-world" },
|
|
80
|
+
* status: {
|
|
81
|
+
* conditions: [{ type: "Ready", status: "True" }],
|
|
82
|
+
* },
|
|
83
|
+
* });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
applyStatus<T extends K8sResource>(
|
|
87
|
+
manifest: ApplyingManifest<T>,
|
|
88
|
+
options?: undefined | ActionOptions
|
|
89
|
+
): Promise<void>;
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Fetches a Kubernetes resource and returns it as a typed object.
|
|
93
|
+
*
|
|
94
|
+
* This is a convenience wrapper over {@link Scenario.assert} that verifies the
|
|
95
|
+
* fetched resource has the expected `apiVersion`, `kind`, and `metadata.name`.
|
|
96
|
+
*
|
|
97
|
+
* This action is retried when it throws.
|
|
98
|
+
*
|
|
99
|
+
* @template T - The expected Kubernetes resource shape.
|
|
100
|
+
* @param resource - Group/version/kind and name of the resource to fetch.
|
|
101
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* type ConfigMap = {
|
|
106
|
+
* apiVersion: "v1";
|
|
107
|
+
* kind: "ConfigMap";
|
|
108
|
+
* metadata: { name: string };
|
|
109
|
+
* data?: Record<string, string>;
|
|
110
|
+
* };
|
|
111
|
+
*
|
|
112
|
+
* const cm = await s.get<ConfigMap>({
|
|
113
|
+
* apiVersion: "v1",
|
|
114
|
+
* kind: "ConfigMap",
|
|
115
|
+
* name: "my-config",
|
|
116
|
+
* });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
get<T extends K8sResource>(
|
|
120
|
+
resource: K8sResourceReference<T>,
|
|
121
|
+
options?: undefined | ActionOptions
|
|
122
|
+
): Promise<T>;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Fetches a Kubernetes resource and runs a test function against it.
|
|
126
|
+
*
|
|
127
|
+
* The `test` callback is invoked with `this` bound to the fetched resource.
|
|
128
|
+
* If the callback throws (or rejects), the assertion fails and the whole
|
|
129
|
+
* action is retried until it succeeds or times out.
|
|
130
|
+
*
|
|
131
|
+
* @template T - The expected Kubernetes resource shape.
|
|
132
|
+
* @param resource - Resource selector and test callback.
|
|
133
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* ```ts
|
|
137
|
+
* await s.assert({
|
|
138
|
+
* apiVersion: "v1",
|
|
139
|
+
* kind: "ConfigMap",
|
|
140
|
+
* name: "my-config",
|
|
141
|
+
* test() {
|
|
142
|
+
* // `this` is the fetched ConfigMap
|
|
143
|
+
* expect(this.data?.mode).toBe("demo");
|
|
144
|
+
* },
|
|
145
|
+
* });
|
|
146
|
+
* ```
|
|
147
|
+
*/
|
|
148
|
+
assert<T extends K8sResource>(
|
|
149
|
+
resource: ResourceTest<T>,
|
|
150
|
+
options?: undefined | ActionOptions
|
|
151
|
+
): Promise<T>;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Lists Kubernetes resources of a given type and runs a test function.
|
|
155
|
+
*
|
|
156
|
+
* The `test` callback is invoked with `this` bound to the fetched list.
|
|
157
|
+
* If the callback throws (or rejects), the assertion fails and the whole
|
|
158
|
+
* action is retried until it succeeds or times out.
|
|
159
|
+
*
|
|
160
|
+
* @template T - The expected Kubernetes resource shape.
|
|
161
|
+
* @param resource - Group/version/kind selector and list test callback.
|
|
162
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```ts
|
|
166
|
+
* await s.assertList({
|
|
167
|
+
* apiVersion: "v1",
|
|
168
|
+
* kind: "ConfigMap",
|
|
169
|
+
* test() {
|
|
170
|
+
* // `this` is the fetched ConfigMap[]
|
|
171
|
+
* const names = this.map((r) => r.metadata.name);
|
|
172
|
+
* expect(names.includes("my-config")).toBe(true);
|
|
173
|
+
* },
|
|
174
|
+
* });
|
|
175
|
+
* ```
|
|
176
|
+
*/
|
|
177
|
+
assertList<T extends K8sResource>(
|
|
178
|
+
resource: ResourceListTest<T>,
|
|
179
|
+
options?: undefined | ActionOptions
|
|
180
|
+
): Promise<Array<T>>;
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Creates a new namespace and returns a namespaced API surface.
|
|
184
|
+
*
|
|
185
|
+
* When `name` is omitted, a unique namespace name is generated (e.g.
|
|
186
|
+
* `kest-abc12`). The namespace creation is a mutating action that registers a
|
|
187
|
+
* cleanup handler; the namespace is deleted during scenario cleanup.
|
|
188
|
+
*
|
|
189
|
+
* @param name - Optional namespace name to create.
|
|
190
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
191
|
+
*
|
|
192
|
+
* @example
|
|
193
|
+
* ```ts
|
|
194
|
+
* const ns = await s.newNamespace();
|
|
195
|
+
* await ns.apply({
|
|
196
|
+
* apiVersion: "v1",
|
|
197
|
+
* kind: "ConfigMap",
|
|
198
|
+
* metadata: { name: "my-config" },
|
|
199
|
+
* data: { mode: "namespaced" },
|
|
200
|
+
* });
|
|
201
|
+
* ```
|
|
202
|
+
*/
|
|
203
|
+
newNamespace(
|
|
204
|
+
name?: undefined | string,
|
|
205
|
+
options?: undefined | ActionOptions
|
|
206
|
+
): Promise<Namespace>;
|
|
207
|
+
|
|
208
|
+
// Shell command actions
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Executes an arbitrary async function within the scenario.
|
|
212
|
+
*
|
|
213
|
+
* This is useful for glue code that doesn't fit into other actions (e.g.
|
|
214
|
+
* preparing fixtures, calling external tools, or making HTTP requests).
|
|
215
|
+
*
|
|
216
|
+
* If `input.revert` is provided, it will be called during scenario cleanup.
|
|
217
|
+
* The `do` function may be retried when it throws, so it should be written to
|
|
218
|
+
* be safe to re-run (idempotent) whenever possible.
|
|
219
|
+
*
|
|
220
|
+
* The execution context provides Bun Shell `$` for running commands.
|
|
221
|
+
*
|
|
222
|
+
* @see https://bun.com/docs/runtime/shell
|
|
223
|
+
*
|
|
224
|
+
* @template T - Value produced by the `do` function.
|
|
225
|
+
* @param input - Execution function and optional cleanup handler.
|
|
226
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* const out = await s.exec({
|
|
231
|
+
* do: async ({ $ }) => {
|
|
232
|
+
* const result = await $`echo hello`;
|
|
233
|
+
* return result.text();
|
|
234
|
+
* },
|
|
235
|
+
* revert: async ({ $ }) => {
|
|
236
|
+
* await $`echo cleanup`;
|
|
237
|
+
* },
|
|
238
|
+
* });
|
|
239
|
+
* ```
|
|
240
|
+
*/
|
|
241
|
+
exec<T>(input: ExecInput<T>, options?: undefined | ActionOptions): Promise<T>;
|
|
242
|
+
|
|
243
|
+
// Multi-cluster actions
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Creates a cluster-bound API surface.
|
|
247
|
+
*
|
|
248
|
+
* The returned {@link Cluster} uses the provided kubeconfig/context for all
|
|
249
|
+
* actions. It does not create any resources by itself.
|
|
250
|
+
*
|
|
251
|
+
* @param cluster - Target kubeconfig and/or context.
|
|
252
|
+
*
|
|
253
|
+
* @example
|
|
254
|
+
* ```ts
|
|
255
|
+
* const c = await s.useCluster({ context: "kind-kind" });
|
|
256
|
+
* const ns = await c.newNamespace("my-ns");
|
|
257
|
+
* await ns.apply({
|
|
258
|
+
* apiVersion: "v1",
|
|
259
|
+
* kind: "ConfigMap",
|
|
260
|
+
* metadata: { name: "my-config" },
|
|
261
|
+
* data: { mode: "cluster" },
|
|
262
|
+
* });
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
useCluster(cluster: ClusterReference): Promise<Cluster>;
|
|
266
|
+
|
|
267
|
+
// BDD(behavior-driven development) actions
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Records a "Given" step for reporting.
|
|
271
|
+
*
|
|
272
|
+
* This does not affect execution; it is used by reporters to render readable
|
|
273
|
+
* test output.
|
|
274
|
+
*
|
|
275
|
+
* @example
|
|
276
|
+
* ```ts
|
|
277
|
+
* s.given("a namespace exists");
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
given(description: string): void;
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Records a "When" step for reporting.
|
|
284
|
+
*
|
|
285
|
+
* @example
|
|
286
|
+
* ```ts
|
|
287
|
+
* s.when("apply a ConfigMap");
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
when(description: string): void;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Records a "Then" step for reporting.
|
|
294
|
+
*
|
|
295
|
+
* @example
|
|
296
|
+
* ```ts
|
|
297
|
+
* s.then("the ConfigMap is present");
|
|
298
|
+
* ```
|
|
299
|
+
*/
|
|
300
|
+
then(description: string): void;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Records an "And" step for reporting.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* s.and("it has expected data");
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
and(description: string): void;
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Records a "But" step for reporting.
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* ```ts
|
|
317
|
+
* s.but("it is not modified by other tests");
|
|
318
|
+
* ```
|
|
319
|
+
*/
|
|
320
|
+
but(description: string): void;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Cluster-bound API surface.
|
|
325
|
+
*
|
|
326
|
+
* This is equivalent to {@link Scenario} basic actions, but with kubectl context
|
|
327
|
+
* bound to a specific Kubernetes cluster.
|
|
328
|
+
*/
|
|
329
|
+
export interface Cluster {
|
|
330
|
+
/**
|
|
331
|
+
* Applies a Kubernetes manifest with `kubectl apply` and registers cleanup.
|
|
332
|
+
*
|
|
333
|
+
* @template T - The expected Kubernetes resource shape.
|
|
334
|
+
* @param manifest - YAML string, resource object, or imported YAML module.
|
|
335
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
336
|
+
*
|
|
337
|
+
* @example
|
|
338
|
+
* ```ts
|
|
339
|
+
* await cluster.apply({
|
|
340
|
+
* apiVersion: "v1",
|
|
341
|
+
* kind: "Namespace",
|
|
342
|
+
* metadata: { name: "my-team" },
|
|
343
|
+
* });
|
|
344
|
+
* ```
|
|
345
|
+
*/
|
|
346
|
+
apply<T extends K8sResource>(
|
|
347
|
+
manifest: ApplyingManifest<T>,
|
|
348
|
+
options?: undefined | ActionOptions
|
|
349
|
+
): Promise<void>;
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Applies the `status` subresource using server-side apply.
|
|
353
|
+
*
|
|
354
|
+
* @template T - The expected Kubernetes resource shape.
|
|
355
|
+
* @param manifest - Resource object that includes a `status` field.
|
|
356
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
357
|
+
*
|
|
358
|
+
* @example
|
|
359
|
+
* ```ts
|
|
360
|
+
* await cluster.applyStatus({
|
|
361
|
+
* apiVersion: "example.com/v1",
|
|
362
|
+
* kind: "HelloWorld",
|
|
363
|
+
* metadata: { name: "my-hello-world" },
|
|
364
|
+
* status: { conditions: [{ type: "Ready", status: "True" }] },
|
|
365
|
+
* });
|
|
366
|
+
* ```
|
|
367
|
+
*/
|
|
368
|
+
applyStatus<T extends K8sResource>(
|
|
369
|
+
manifest: ApplyingManifest<T>,
|
|
370
|
+
options?: undefined | ActionOptions
|
|
371
|
+
): Promise<void>;
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Fetches a Kubernetes resource by GVK and name.
|
|
375
|
+
*
|
|
376
|
+
* @template T - The expected Kubernetes resource shape.
|
|
377
|
+
* @param resource - Group/version/kind and name of the resource to fetch.
|
|
378
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* const ns = await cluster.get({
|
|
383
|
+
* apiVersion: "v1",
|
|
384
|
+
* kind: "Namespace",
|
|
385
|
+
* name: "default",
|
|
386
|
+
* });
|
|
387
|
+
* ```
|
|
388
|
+
*/
|
|
389
|
+
get<T extends K8sResource>(
|
|
390
|
+
resource: K8sResourceReference<T>,
|
|
391
|
+
options?: undefined | ActionOptions
|
|
392
|
+
): Promise<T>;
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* Fetches a Kubernetes resource and runs a test function against it.
|
|
396
|
+
*
|
|
397
|
+
* @template T - The expected Kubernetes resource shape.
|
|
398
|
+
* @param resource - Resource selector and test callback.
|
|
399
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
400
|
+
*
|
|
401
|
+
* @example
|
|
402
|
+
* ```ts
|
|
403
|
+
* await cluster.assert({
|
|
404
|
+
* apiVersion: "v1",
|
|
405
|
+
* kind: "Namespace",
|
|
406
|
+
* name: "kube-system",
|
|
407
|
+
* test() {
|
|
408
|
+
* expect(this.metadata.name).toBe("kube-system");
|
|
409
|
+
* },
|
|
410
|
+
* });
|
|
411
|
+
* ```
|
|
412
|
+
*/
|
|
413
|
+
assert<T extends K8sResource>(
|
|
414
|
+
resource: ResourceTest<T>,
|
|
415
|
+
options?: undefined | ActionOptions
|
|
416
|
+
): Promise<T>;
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Lists Kubernetes resources of a given type and runs a test function.
|
|
420
|
+
*
|
|
421
|
+
* @template T - The expected Kubernetes resource shape.
|
|
422
|
+
* @param resource - Group/version/kind selector and list test callback.
|
|
423
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* ```ts
|
|
427
|
+
* await cluster.assertList({
|
|
428
|
+
* apiVersion: "v1",
|
|
429
|
+
* kind: "Namespace",
|
|
430
|
+
* test() {
|
|
431
|
+
* expect(this.length > 0).toBe(true);
|
|
432
|
+
* },
|
|
433
|
+
* });
|
|
434
|
+
* ```
|
|
435
|
+
*/
|
|
436
|
+
assertList<T extends K8sResource>(
|
|
437
|
+
resource: ResourceListTest<T>,
|
|
438
|
+
options?: undefined | ActionOptions
|
|
439
|
+
): Promise<Array<T>>;
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Creates a new namespace in this cluster and returns a namespaced API.
|
|
443
|
+
*
|
|
444
|
+
* @param name - Optional namespace name to create.
|
|
445
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* ```ts
|
|
449
|
+
* const ns = await cluster.newNamespace("my-ns");
|
|
450
|
+
* await ns.apply({
|
|
451
|
+
* apiVersion: "v1",
|
|
452
|
+
* kind: "ConfigMap",
|
|
453
|
+
* metadata: { name: "my-config" },
|
|
454
|
+
* data: { mode: "from-cluster" },
|
|
455
|
+
* });
|
|
456
|
+
* ```
|
|
457
|
+
*/
|
|
458
|
+
newNamespace(
|
|
459
|
+
name?: undefined | string,
|
|
460
|
+
options?: undefined | ActionOptions
|
|
461
|
+
): Promise<Namespace>;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* Namespace-bound API surface.
|
|
466
|
+
*
|
|
467
|
+
* A {@link Namespace} is typically obtained via {@link Scenario.newNamespace} or
|
|
468
|
+
* {@link Cluster.newNamespace}.
|
|
469
|
+
*
|
|
470
|
+
* Operations are scoped by setting the kubectl namespace context (equivalent to
|
|
471
|
+
* passing `kubectl -n <namespace>`).
|
|
472
|
+
*
|
|
473
|
+
* Kest does not rewrite your manifests. For write operations
|
|
474
|
+
* ({@link Namespace.apply} and {@link Namespace.applyStatus}), treat this API as
|
|
475
|
+
* the source of truth for the target namespace:
|
|
476
|
+
*
|
|
477
|
+
* - Prefer omitting `metadata.namespace` in manifests; kubectl will apply the
|
|
478
|
+
* resource into this namespace.
|
|
479
|
+
* - If `metadata.namespace` is set, it must match this namespace. A mismatch
|
|
480
|
+
* causes `kubectl` to fail.
|
|
481
|
+
*/
|
|
482
|
+
export interface Namespace {
|
|
483
|
+
/**
|
|
484
|
+
* Applies a Kubernetes manifest in this namespace and registers cleanup.
|
|
485
|
+
*
|
|
486
|
+
* The target namespace is controlled by this {@link Namespace} instance.
|
|
487
|
+
* Prefer omitting `manifest.metadata.namespace`; if it is set, it must match
|
|
488
|
+
* this namespace (otherwise `kubectl` fails).
|
|
489
|
+
*
|
|
490
|
+
* @template T - The expected Kubernetes resource shape.
|
|
491
|
+
* @param manifest - YAML string, resource object, or imported YAML module.
|
|
492
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* ```ts
|
|
496
|
+
* const ns = await s.newNamespace("my-ns");
|
|
497
|
+
* await ns.apply({
|
|
498
|
+
* apiVersion: "v1",
|
|
499
|
+
* kind: "Secret",
|
|
500
|
+
* metadata: { name: "my-secret" },
|
|
501
|
+
* type: "Opaque",
|
|
502
|
+
* stringData: { password: "s3cr3t" },
|
|
503
|
+
* });
|
|
504
|
+
* ```
|
|
505
|
+
*/
|
|
506
|
+
apply<T extends K8sResource>(
|
|
507
|
+
manifest: ApplyingManifest<T>,
|
|
508
|
+
options?: undefined | ActionOptions
|
|
509
|
+
): Promise<void>;
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Applies the `status` subresource in this namespace using server-side apply.
|
|
513
|
+
*
|
|
514
|
+
* The target namespace is controlled by this {@link Namespace} instance.
|
|
515
|
+
* Prefer omitting `manifest.metadata.namespace`; if it is set, it must match
|
|
516
|
+
* this namespace (otherwise `kubectl` fails).
|
|
517
|
+
*
|
|
518
|
+
* @template T - The expected Kubernetes resource shape.
|
|
519
|
+
* @param manifest - Resource object that includes a `status` field.
|
|
520
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
521
|
+
*
|
|
522
|
+
* @example
|
|
523
|
+
* ```ts
|
|
524
|
+
* await ns.applyStatus({
|
|
525
|
+
* apiVersion: "example.com/v1",
|
|
526
|
+
* kind: "HelloWorld",
|
|
527
|
+
* metadata: { name: "my-hello-world" },
|
|
528
|
+
* status: { conditions: [{ type: "Ready", status: "True" }] },
|
|
529
|
+
* });
|
|
530
|
+
* ```
|
|
531
|
+
*/
|
|
532
|
+
applyStatus<T extends K8sResource>(
|
|
533
|
+
manifest: ApplyingManifest<T>,
|
|
534
|
+
options?: undefined | ActionOptions
|
|
535
|
+
): Promise<void>;
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Fetches a namespaced Kubernetes resource by GVK and name.
|
|
539
|
+
*
|
|
540
|
+
* @template T - The expected Kubernetes resource shape.
|
|
541
|
+
* @param resource - Group/version/kind and name of the resource to fetch.
|
|
542
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
543
|
+
*
|
|
544
|
+
* @example
|
|
545
|
+
* ```ts
|
|
546
|
+
* const cm = await ns.get({
|
|
547
|
+
* apiVersion: "v1",
|
|
548
|
+
* kind: "ConfigMap",
|
|
549
|
+
* name: "my-config",
|
|
550
|
+
* });
|
|
551
|
+
* ```
|
|
552
|
+
*/
|
|
553
|
+
get<T extends K8sResource>(
|
|
554
|
+
resource: K8sResourceReference<T>,
|
|
555
|
+
options?: undefined | ActionOptions
|
|
556
|
+
): Promise<T>;
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Fetches a namespaced Kubernetes resource and runs a test function.
|
|
560
|
+
*
|
|
561
|
+
* @template T - The expected Kubernetes resource shape.
|
|
562
|
+
* @param resource - Resource selector and test callback.
|
|
563
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```ts
|
|
567
|
+
* await ns.assert({
|
|
568
|
+
* apiVersion: "v1",
|
|
569
|
+
* kind: "ConfigMap",
|
|
570
|
+
* name: "my-config",
|
|
571
|
+
* test() {
|
|
572
|
+
* expect(this.data !== undefined).toBe(true);
|
|
573
|
+
* },
|
|
574
|
+
* });
|
|
575
|
+
* ```
|
|
576
|
+
*/
|
|
577
|
+
assert<T extends K8sResource>(
|
|
578
|
+
resource: ResourceTest<T>,
|
|
579
|
+
options?: undefined | ActionOptions
|
|
580
|
+
): Promise<T>;
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Lists namespaced Kubernetes resources of a given type and runs a test.
|
|
584
|
+
*
|
|
585
|
+
* @template T - The expected Kubernetes resource shape.
|
|
586
|
+
* @param resource - Group/version/kind selector and list test callback.
|
|
587
|
+
* @param options - Retry options such as timeout and polling interval.
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* ```ts
|
|
591
|
+
* await ns.assertList({
|
|
592
|
+
* apiVersion: "v1",
|
|
593
|
+
* kind: "ConfigMap",
|
|
594
|
+
* test() {
|
|
595
|
+
* expect(this.some((c) => c.metadata.name === "my-config")).toBe(true);
|
|
596
|
+
* },
|
|
597
|
+
* });
|
|
598
|
+
* ```
|
|
599
|
+
*/
|
|
600
|
+
assertList<T extends K8sResource>(
|
|
601
|
+
resource: ResourceListTest<T>,
|
|
602
|
+
options?: undefined | ActionOptions
|
|
603
|
+
): Promise<Array<T>>;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Retry configuration for scenario actions.
|
|
608
|
+
*
|
|
609
|
+
* These options are forwarded to Kest's retry mechanism.
|
|
610
|
+
*
|
|
611
|
+
* - `timeout` defaults to `"5s"`
|
|
612
|
+
* - `interval` defaults to `"200ms"`
|
|
613
|
+
*
|
|
614
|
+
* Durations are expressed as strings such as `"30s"`, `"200ms"`, or `"1m"`.
|
|
615
|
+
*/
|
|
616
|
+
export interface ActionOptions {
|
|
617
|
+
/**
|
|
618
|
+
* Maximum duration to keep retrying an action.
|
|
619
|
+
*/
|
|
620
|
+
readonly timeout?: undefined | string;
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Delay between retry attempts.
|
|
624
|
+
*/
|
|
625
|
+
readonly interval?: undefined | string;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Input to {@link Scenario.exec}.
|
|
630
|
+
*/
|
|
631
|
+
export interface ExecInput<T = unknown> {
|
|
632
|
+
/**
|
|
633
|
+
* Execute arbitrary processing and return its value.
|
|
634
|
+
*
|
|
635
|
+
* Note: this function may be retried when it throws and `options.timeout`
|
|
636
|
+
* allows it (same as other actions), so prefer idempotent operations.
|
|
637
|
+
*/
|
|
638
|
+
readonly do: (context: ExecContext) => Promise<T>;
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Optional cleanup invoked during scenario cleanup (revert phase).
|
|
642
|
+
*
|
|
643
|
+
* When omitted, no cleanup is performed.
|
|
644
|
+
*/
|
|
645
|
+
readonly revert?: undefined | ((context: ExecContext) => Promise<void>);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
/**
|
|
649
|
+
* Context object passed to {@link ExecInput.do} and {@link ExecInput.revert}.
|
|
650
|
+
*/
|
|
651
|
+
export interface ExecContext {
|
|
652
|
+
/**
|
|
653
|
+
* Bun shell helper from `import { $ } from "bun"`.
|
|
654
|
+
*
|
|
655
|
+
* @see https://bun.com/docs/runtime/shell
|
|
656
|
+
*/
|
|
657
|
+
readonly $: BunDollar;
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/**
|
|
661
|
+
* Identifies a Kubernetes resource by group/version/kind and name.
|
|
662
|
+
*
|
|
663
|
+
* Used by {@link Scenario.get}.
|
|
664
|
+
*/
|
|
665
|
+
export interface K8sResourceReference<T extends K8sResource = K8sResource> {
|
|
666
|
+
/**
|
|
667
|
+
* Kubernetes API version (e.g. `"v1"`, `"apps/v1"`).
|
|
668
|
+
*/
|
|
669
|
+
readonly apiVersion: T["apiVersion"];
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Kubernetes kind (e.g. `"ConfigMap"`, `"Deployment"`).
|
|
673
|
+
*/
|
|
674
|
+
readonly kind: T["kind"];
|
|
675
|
+
|
|
676
|
+
/**
|
|
677
|
+
* `metadata.name` of the target resource.
|
|
678
|
+
*/
|
|
679
|
+
readonly name: string;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/**
|
|
683
|
+
* A test definition for {@link Scenario.assert}.
|
|
684
|
+
*/
|
|
685
|
+
export interface ResourceTest<T extends K8sResource = K8sResource> {
|
|
686
|
+
/**
|
|
687
|
+
* Kubernetes API version (e.g. `"v1"`, `"apps/v1"`).
|
|
688
|
+
*/
|
|
689
|
+
readonly apiVersion: T["apiVersion"];
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Kubernetes kind (e.g. `"ConfigMap"`, `"Deployment"`).
|
|
693
|
+
*/
|
|
694
|
+
readonly kind: T["kind"];
|
|
695
|
+
|
|
696
|
+
/**
|
|
697
|
+
* `metadata.name` of the target resource.
|
|
698
|
+
*/
|
|
699
|
+
readonly name: string;
|
|
700
|
+
|
|
701
|
+
/**
|
|
702
|
+
* Assertion callback.
|
|
703
|
+
*
|
|
704
|
+
* The callback is invoked with `this` bound to the fetched resource.
|
|
705
|
+
* Throwing (or rejecting) signals a failed assertion.
|
|
706
|
+
*/
|
|
707
|
+
readonly test: (this: T, resource: T) => unknown | Promise<unknown>;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/**
|
|
711
|
+
* A test definition for {@link Scenario.assertList}.
|
|
712
|
+
*/
|
|
713
|
+
export interface ResourceListTest<T extends K8sResource = K8sResource> {
|
|
714
|
+
/**
|
|
715
|
+
* Kubernetes API version (e.g. `"v1"`, `"apps/v1"`).
|
|
716
|
+
*/
|
|
717
|
+
readonly apiVersion: T["apiVersion"];
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Kubernetes kind (e.g. `"ConfigMap"`, `"Deployment"`).
|
|
721
|
+
*/
|
|
722
|
+
readonly kind: T["kind"];
|
|
723
|
+
|
|
724
|
+
/**
|
|
725
|
+
* Assertion callback.
|
|
726
|
+
*
|
|
727
|
+
* The callback is invoked with `this` bound to the fetched resource list.
|
|
728
|
+
* Throwing (or rejecting) signals a failed assertion.
|
|
729
|
+
*/
|
|
730
|
+
readonly test: (
|
|
731
|
+
this: Array<T>,
|
|
732
|
+
resources: Array<T>
|
|
733
|
+
) => unknown | Promise<unknown>;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* Kubernetes cluster selector for {@link Scenario.useCluster}.
|
|
738
|
+
*/
|
|
739
|
+
export interface ClusterReference {
|
|
740
|
+
/**
|
|
741
|
+
* Path to a kubeconfig file to use for this cluster.
|
|
742
|
+
*/
|
|
743
|
+
readonly kubeconfig?: undefined | string;
|
|
744
|
+
|
|
745
|
+
/**
|
|
746
|
+
* kubeconfig context name to use for this cluster.
|
|
747
|
+
*/
|
|
748
|
+
readonly context?: undefined | string;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
/**
|
|
752
|
+
* A Kubernetes manifest accepted by Kest actions.
|
|
753
|
+
*
|
|
754
|
+
* This flexibility is intended to make tests ergonomic:
|
|
755
|
+
*
|
|
756
|
+
* - pass YAML as a string
|
|
757
|
+
* - pass an object literal
|
|
758
|
+
* - `import manifest from "./resource.yaml"` and pass the module
|
|
759
|
+
*/
|
|
760
|
+
export type ApplyingManifest<T extends K8sResource = K8sResource> =
|
|
761
|
+
| string // YAML string
|
|
762
|
+
| T
|
|
763
|
+
| ImportedYaml
|
|
764
|
+
| Promise<ImportedYaml>;
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Minimal shape of a Kubernetes resource.
|
|
768
|
+
*
|
|
769
|
+
* Kest treats resources as plain objects and only relies on a few common fields
|
|
770
|
+
* (`apiVersion`, `kind`, and `metadata.name`).
|
|
771
|
+
*/
|
|
772
|
+
export interface K8sResource {
|
|
773
|
+
apiVersion: string;
|
|
774
|
+
kind: string;
|
|
775
|
+
metadata: {
|
|
776
|
+
name: string;
|
|
777
|
+
namespace?: string;
|
|
778
|
+
[key: string]: unknown;
|
|
779
|
+
};
|
|
780
|
+
[key: string]: unknown;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/**
|
|
784
|
+
* Shape of `import manifest from "./resource.yaml"`.
|
|
785
|
+
*/
|
|
786
|
+
export interface ImportedYaml {
|
|
787
|
+
readonly default: unknown;
|
|
788
|
+
}
|