1import * as Alchemy from "alchemy";
2import * as Kubernetes from "alchemy/Kubernetes";
3import { connectCluster, readObject } from "alchemy/Kubernetes/internal/client";
4import * as Test from "alchemy/Test/Bun";
5import { expect } from "bun:test";
6import * as Console from "effect/Console";
7import * as Effect from "effect/Effect";
8import * as Schedule from "effect/Schedule";
9import Stack from "../alchemy.run.ts";
10
11const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
12 providers: Kubernetes.providers(),
13 state: Alchemy.localState(),
14});
15
16const stack = beforeAll(
17 destroy(Stack).pipe(Effect.andThen(deploy(Stack)), Effect.tap(Console.log)),
18 { timeout: 240_000 },
19);
20afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack), {
21 timeout: 120_000,
22});
23
24// The cluster the tutorial creates, reached the way `kubectl` would.
25const cluster = Kubernetes.KubeConfig({ context: "kind-alchemy" });
26
27const read = (object: {
28 apiVersion: string;
29 kind: string;
30 name: string;
31 namespace?: string;
32}) =>
33 connectCluster(cluster).pipe(
34 Effect.flatMap((transport) => readObject({ transport, object })),
35 Effect.provide(Kubernetes.builtinAdapters()),
36 );
37
38test(
39 "the smoke test Job reaches podinfo and completes",
40 Effect.gen(function* () {
41 const { namespace, smokeTest } = yield* stack;
42 const job = (yield* read({
43 apiVersion: "batch/v1",
44 kind: "Job",
45 name: smokeTest,
46 namespace,
47 }).pipe(
48 Effect.repeat({
49 schedule: Schedule.spaced("2 seconds"),
50 until: (job) =>
51 ((job as { status?: { succeeded?: number } } | undefined)?.status
52 ?.succeeded ?? 0) >= 1,
53 times: 60,
54 }),
55 )) as { status?: { succeeded?: number } };
56 expect(job.status?.succeeded).toBe(1);
57 }),
58 { timeout: 180_000 },
59);
60
61test(
62 "podinfo, the health-check CronJob, and metrics-server are applied",
63 Effect.gen(function* () {
64 const { namespace, service } = yield* stack;
65 const web = (yield* read({
66 apiVersion: "apps/v1",
67 kind: "Deployment",
68 name: service,
69 namespace,
70 })) as { spec?: { replicas?: number } };
71 expect(web.spec?.replicas).toBe(2);
72
73 const cronJob = (yield* read({
74 apiVersion: "batch/v1",
75 kind: "CronJob",
76 name: "health-check",
77 namespace,
78 })) as { spec?: { schedule?: string } };
79 expect(cronJob.spec?.schedule).toBe("*/5 * * * *");
80
81 const metrics = yield* read({
82 apiVersion: "apps/v1",
83 kind: "Deployment",
84 name: "metrics-server",
85 namespace: "kube-system",
86 });
87 expect(metrics).toBeDefined();
88 }),
89 { timeout: 60_000 },
90);