@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.
@@ -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
+ }