1import * as AWS from "@/AWS";
2import { HealthCheck } from "@/AWS/Route53";
3import * as Test from "@/Test/Alchemy";
4import * as route53 from "@distilled.cloud/aws/route-53";
5import { expect } from "alchemy-test";
6import * as Effect from "effect/Effect";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: AWS.providers() });
10
11const assertCheckGone = (id: string) =>
12 route53.getHealthCheck({ HealthCheckId: id }).pipe(
13 Effect.flatMap(() => Effect.fail(new Error("health check still exists"))),
14 Effect.catchTag("NoSuchHealthCheck", () => Effect.void),
15 Effect.retry({
16 while: (e) => e instanceof Error,
17 schedule: Schedule.max([
18 Schedule.fixed("2 seconds"),
19 Schedule.recurs(10),
20 ]),
21 }),
22 );
23
24test.provider(
25 "create, update in place, tag, and delete health check",
26 (stack) =>
27 Effect.gen(function* () {
28 yield* stack.destroy();
29
30 // Create.
31 const check = yield* stack.deploy(
32 Effect.gen(function* () {
33 return yield* HealthCheck("Check", {
34 type: "HTTP",
35 fullyQualifiedDomainName: "example.com",
36 resourcePath: "/",
37 port: 80,
38 requestInterval: "30 seconds",
39 failureThreshold: 3,
40 tags: { env: "test" },
41 });
42 }),
43 );
44
45 expect(check.id).toBeDefined();
46 expect(check.healthCheckId).toBe(check.id);
47 expect(check.type).toBe("HTTP");
48
49 const observed = yield* route53.getHealthCheck({
50 HealthCheckId: check.id,
51 });
52 expect(observed.HealthCheck.HealthCheckConfig.FailureThreshold).toBe(3);
53 expect(observed.HealthCheck.HealthCheckConfig.ResourcePath).toBe("/");
54 // Duration.Input prop round-trips to the wire as integer seconds.
55 expect(observed.HealthCheck.HealthCheckConfig.RequestInterval).toBe(30);
56
57 const tags = yield* route53.listTagsForResource({
58 ResourceType: "healthcheck",
59 ResourceId: check.id,
60 });
61 const tagMap = Object.fromEntries(
62 (tags.ResourceTagSet.Tags ?? []).map((t) => [t.Key, t.Value]),
63 );
64 expect(tagMap.env).toBe("test");
65 expect(tagMap["alchemy::id"]).toBeDefined();
66
67 // Update mutable fields in place (version-locked).
68 const updated = yield* stack.deploy(
69 Effect.gen(function* () {
70 return yield* HealthCheck("Check", {
71 type: "HTTP",
72 fullyQualifiedDomainName: "example.com",
73 resourcePath: "/health",
74 port: 80,
75 requestInterval: "30 seconds",
76 failureThreshold: 5,
77 tags: { env: "prod" },
78 });
79 }),
80 );
81 // In-place update keeps the same id.
82 expect(updated.id).toBe(check.id);
83
84 const observed2 = yield* route53.getHealthCheck({
85 HealthCheckId: check.id,
86 });
87 expect(observed2.HealthCheck.HealthCheckConfig.FailureThreshold).toBe(5);
88 expect(observed2.HealthCheck.HealthCheckConfig.ResourcePath).toBe(
89 "/health",
90 );
91
92 const tags2 = yield* route53.listTagsForResource({
93 ResourceType: "healthcheck",
94 ResourceId: check.id,
95 });
96 const tagMap2 = Object.fromEntries(
97 (tags2.ResourceTagSet.Tags ?? []).map((t) => [t.Key, t.Value]),
98 );
99 expect(tagMap2.env).toBe("prod");
100
101 yield* stack.destroy();
102 yield* assertCheckGone(check.id);
103 }),
104 { tags: ["provider:aws", "provider:aws:route53", "live"], timeout: 180_000 },
105);
106
107test.provider(
108 "changing Type replaces the health check",
109 (stack) =>
110 Effect.gen(function* () {
111 yield* stack.destroy();
112
113 const check = yield* stack.deploy(
114 Effect.gen(function* () {
115 return yield* HealthCheck("ReplaceCheck", {
116 type: "HTTP",
117 fullyQualifiedDomainName: "example.com",
118 port: 80,
119 requestInterval: "30 seconds",
120 });
121 }),
122 );
123 const originalId = check.id;
124
125 // Type is immutable — changing it forces replacement (new id).
126 const replaced = yield* stack.deploy(
127 Effect.gen(function* () {
128 return yield* HealthCheck("ReplaceCheck", {
129 type: "HTTPS",
130 fullyQualifiedDomainName: "example.com",
131 port: 443,
132 requestInterval: "30 seconds",
133 });
134 }),
135 );
136
137 expect(replaced.type).toBe("HTTPS");
138 expect(replaced.id).not.toBe(originalId);
139
140 // The old health check must have been deleted.
141 yield* assertCheckGone(originalId);
142
143 yield* stack.destroy();
144 yield* assertCheckGone(replaced.id);
145 }),
146 { tags: ["provider:aws", "provider:aws:route53", "live"], timeout: 180_000 },
147);