@appthrust/kest 0.3.2 → 0.4.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 +33 -0
- package/package.json +1 -1
- package/ts/actions/create-namespace.ts +3 -11
- package/ts/apis/index.ts +28 -0
- package/ts/naming/index.ts +27 -0
- package/ts/scenario/index.ts +2 -0
package/README.md
CHANGED
|
@@ -440,6 +440,7 @@ The top-level API surface available in every test callback.
|
|
|
440
440
|
| `assertAbsence(resource, options?)` | Assert that a resource does not exist |
|
|
441
441
|
| `assertList(resource, options?)` | Fetch a list of resources and run assertions |
|
|
442
442
|
| `newNamespace(name?, options?)` | Create an ephemeral namespace (supports `{ generateName }`) |
|
|
443
|
+
| `generateName(prefix)` | Generate a random-suffix name (statistical uniqueness) |
|
|
443
444
|
| `exec(input, options?)` | Execute shell commands with optional revert |
|
|
444
445
|
| `useCluster(ref)` | Create a cluster-bound API surface |
|
|
445
446
|
| `given(desc)` / `when(desc)` / `then(desc)` / `and(desc)` / `but(desc)` | BDD annotations for reporting |
|
|
@@ -459,6 +460,38 @@ All actions accept an optional options object for retry configuration.
|
|
|
459
460
|
|
|
460
461
|
Duration strings support units like `"200ms"`, `"5s"`, `"1m"`.
|
|
461
462
|
|
|
463
|
+
## Best Practices
|
|
464
|
+
|
|
465
|
+
### Avoiding naming collisions between tests
|
|
466
|
+
|
|
467
|
+
When tests run in parallel, hard-coded resource names can collide (especially when you create cluster-scoped resources).
|
|
468
|
+
|
|
469
|
+
Kest offers a few ways to avoid these collisions:
|
|
470
|
+
|
|
471
|
+
- Use `s.newNamespace()` to isolate namespaced resources per test (recommended default).
|
|
472
|
+
- Use `s.newNamespace({ generateName: "prefix-" })` to keep isolation while making the namespace name easier to recognize in logs/reports.
|
|
473
|
+
- Use `s.generateName("prefix-")` to generate a random-suffix name when you need additional names outside of `newNamespace` (e.g. cluster-scoped resources).
|
|
474
|
+
|
|
475
|
+
`s.newNamespace(...)` actually creates the `Namespace` via `kubectl create` and retries on name collisions (regenerating a new name each attempt), so once it succeeds the namespace name is unique in the cluster. `s.generateName(...)` is a pure string helper and provides **statistical uniqueness** only (collisions are extremely unlikely, but not impossible).
|
|
476
|
+
|
|
477
|
+
```ts
|
|
478
|
+
s.given("a cluster-scoped resource name should not collide with other tests");
|
|
479
|
+
const roleName = s.generateName("kest-e2e-role-");
|
|
480
|
+
|
|
481
|
+
await s.create({
|
|
482
|
+
apiVersion: "rbac.authorization.k8s.io/v1",
|
|
483
|
+
kind: "ClusterRole",
|
|
484
|
+
metadata: { name: roleName },
|
|
485
|
+
rules: [
|
|
486
|
+
{
|
|
487
|
+
apiGroups: [""],
|
|
488
|
+
resources: ["configmaps"],
|
|
489
|
+
verbs: ["get", "list"],
|
|
490
|
+
},
|
|
491
|
+
],
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
462
495
|
## Type Safety
|
|
463
496
|
|
|
464
497
|
Define TypeScript interfaces for your Kubernetes resources to get full type checking in manifests and assertions:
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { create } from "./create";
|
|
2
2
|
import type { MutateDef } from "./types";
|
|
3
|
+
import { generateName } from "../naming";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Input for namespace creation.
|
|
@@ -43,19 +44,10 @@ export const createNamespace = {
|
|
|
43
44
|
|
|
44
45
|
function resolveNamespaceName(input: CreateNamespaceInput): string {
|
|
45
46
|
if (input === undefined) {
|
|
46
|
-
return
|
|
47
|
+
return generateName("kest-", 5);
|
|
47
48
|
}
|
|
48
49
|
if (typeof input === "string") {
|
|
49
50
|
return input;
|
|
50
51
|
}
|
|
51
|
-
return
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function randomConsonantDigits(length = 8): string {
|
|
55
|
-
const chars = "bcdfghjklmnpqrstvwxyz0123456789";
|
|
56
|
-
let result = "";
|
|
57
|
-
for (let i = 0; i < length; i++) {
|
|
58
|
-
result += chars[Math.floor(Math.random() * chars.length)];
|
|
59
|
-
}
|
|
60
|
-
return result;
|
|
52
|
+
return generateName(input.generateName, 5);
|
|
61
53
|
}
|
package/ts/apis/index.ts
CHANGED
|
@@ -332,6 +332,34 @@ export interface Scenario {
|
|
|
332
332
|
options?: undefined | ActionOptions
|
|
333
333
|
): Promise<Namespace>;
|
|
334
334
|
|
|
335
|
+
/**
|
|
336
|
+
* Generates a random, Kubernetes-friendly name with the given prefix.
|
|
337
|
+
*
|
|
338
|
+
* This is a pure helper (no kubectl calls). Useful when you need multiple
|
|
339
|
+
* unique names within a single scenario, especially when you need custom
|
|
340
|
+
* metadata (e.g. labels) and can't use {@link Scenario.newNamespace}.
|
|
341
|
+
*
|
|
342
|
+
* @example
|
|
343
|
+
* ```ts
|
|
344
|
+
* // Useful for cluster-scoped resources (names must be unique cluster-wide)
|
|
345
|
+
* const roleName = s.generateName("kest-e2e-role-");
|
|
346
|
+
*
|
|
347
|
+
* await s.create({
|
|
348
|
+
* apiVersion: "rbac.authorization.k8s.io/v1",
|
|
349
|
+
* kind: "ClusterRole",
|
|
350
|
+
* metadata: { name: roleName },
|
|
351
|
+
* rules: [
|
|
352
|
+
* {
|
|
353
|
+
* apiGroups: [""],
|
|
354
|
+
* resources: ["configmaps"],
|
|
355
|
+
* verbs: ["get", "list"],
|
|
356
|
+
* },
|
|
357
|
+
* ],
|
|
358
|
+
* });
|
|
359
|
+
* ```
|
|
360
|
+
*/
|
|
361
|
+
generateName(prefix: string): string;
|
|
362
|
+
|
|
335
363
|
// Shell command actions
|
|
336
364
|
|
|
337
365
|
/**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const consonantDigits = "bcdfghjklmnpqrstvwxyz0123456789";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Generates a random string consisting of consonants and digits.
|
|
5
|
+
*
|
|
6
|
+
* This is intended for Kubernetes resource names to avoid accidental words.
|
|
7
|
+
*/
|
|
8
|
+
export function randomConsonantDigits(length = 8): string {
|
|
9
|
+
let result = "";
|
|
10
|
+
for (let i = 0; i < length; i++) {
|
|
11
|
+
result += consonantDigits[Math.floor(Math.random() * consonantDigits.length)];
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Generates a Kubernetes-friendly name by appending a random suffix.
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* generateName("foo-"); // "foo-d7kpn"
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function generateName(prefix: string, suffixLength = 5): string {
|
|
25
|
+
return `${prefix}${randomConsonantDigits(suffixLength)}`;
|
|
26
|
+
}
|
|
27
|
+
|
package/ts/scenario/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import type {
|
|
|
22
22
|
} from "../apis";
|
|
23
23
|
import bdd from "../bdd";
|
|
24
24
|
import type { Kubectl } from "../kubectl";
|
|
25
|
+
import { generateName as generateRandomName } from "../naming";
|
|
25
26
|
import type { Recorder } from "../recording";
|
|
26
27
|
import type { Reporter } from "../reporter/interface";
|
|
27
28
|
import { retryUntil } from "../retry";
|
|
@@ -51,6 +52,7 @@ export function createScenario(deps: CreateScenarioOptions): InternalScenario {
|
|
|
51
52
|
then: bdd.then(deps),
|
|
52
53
|
and: bdd.and(deps),
|
|
53
54
|
but: bdd.but(deps),
|
|
55
|
+
generateName: (prefix: string) => generateRandomName(prefix),
|
|
54
56
|
newNamespace: createNewNamespaceFn(deps),
|
|
55
57
|
useCluster: createUseClusterFn(deps),
|
|
56
58
|
async cleanup() {
|