Open History to see the last commit
1import * as Hetzner from "@alchemy.run/hetzner";
2import { expect } from "@alchemy.run/test";
3import * as Test from "@alchemy.run/test/Alchemy";
4import * as floatingIps from "@distilled.cloud/hetzner/floating_ips";
5import * as Provider from "alchemy/Provider";
6import * as Effect from "effect/Effect";
7import { MinimumLogLevel } from "effect/References";
8import * as Schedule from "effect/Schedule";
9
10const { test } = Test.make({ providers: Hetzner.providers() });
11
12const logLevel = Effect.provideService(MinimumLogLevel, process.env.DEBUG ? "Debug" : "Info");
13
14const hasHetznerCreds = !!process.env.HCLOUD_TOKEN;
15
16const waitUntilGone = (id: number) =>
17 floatingIps.getFloatingIp({ id }).pipe(
18 Effect.as("found" as const),
19 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
20 Effect.repeat({
21 schedule: Schedule.spaced("1 second"),
22 until: (status) => status === "gone",
23 times: 10,
24 }),
25 );
26
27test.provider.skipIf(!hasHetznerCreds)(
28 "create update delete unassigned ipv4",
29 (stack) =>
30 Effect.gen(function* () {
31 yield* stack.destroy();
32
33 const created = yield* stack.deploy(
34 Effect.gen(function* () {
35 return yield* Hetzner.FloatingIp("PublicIp", {
36 type: "ipv4",
37 homeLocation: "nbg1",
38 description: "alchemy floating ip create",
39 labels: { env: "test" },
40 });
41 }),
42 );
43
44 expect(created.id).toEqual(expect.any(Number));
45 expect(created.name).toEqual(expect.any(String));
46 expect(created.type).toEqual("ipv4");
47 expect(created.ip).toEqual(expect.any(String));
48 expect(created.homeLocation).toEqual("nbg1");
49 expect(created.homeLocationId).toEqual(expect.any(Number));
50 expect(created.description).toEqual("alchemy floating ip create");
51 expect(created.deleteProtection).toEqual(false);
52 expect(created.serverId).toBeNull();
53 expect(created.labels).toMatchObject({ env: "test" });
54
55 const fetched = yield* floatingIps.getFloatingIp({
56 id: created.id,
57 });
58 expect(fetched.floating_ip.id).toEqual(created.id);
59 expect(fetched.floating_ip.type).toEqual("ipv4");
60 expect(fetched.floating_ip.ip).toEqual(created.ip);
61 expect(fetched.floating_ip.home_location.name).toEqual("nbg1");
62 expect(fetched.floating_ip.description).toEqual("alchemy floating ip create");
63 expect(fetched.floating_ip.server).toBeNull();
64 expect(fetched.floating_ip.labels.env).toEqual("test");
65
66 const updated = yield* stack.deploy(
67 Effect.gen(function* () {
68 return yield* Hetzner.FloatingIp("PublicIp", {
69 type: "ipv4",
70 homeLocation: "nbg1",
71 description: "alchemy floating ip update",
72 labels: { env: "prod", role: "edge" },
73 });
74 }),
75 );
76
77 expect(updated.id).toEqual(created.id);
78 expect(updated.ip).toEqual(created.ip);
79 expect(updated.description).toEqual("alchemy floating ip update");
80 expect(updated.labels).toMatchObject({ env: "prod", role: "edge" });
81
82 const refetched = yield* floatingIps.getFloatingIp({
83 id: updated.id,
84 });
85 expect(refetched.floating_ip.description).toEqual("alchemy floating ip update");
86 expect(refetched.floating_ip.labels.env).toEqual("prod");
87 expect(refetched.floating_ip.labels.role).toEqual("edge");
88
89 yield* stack.destroy();
90
91 const gone = yield* waitUntilGone(created.id);
92 expect(gone).toEqual("gone");
93 }).pipe(logLevel),
94 {
95 tags: ["provider:hetzner", "provider:hetzner:floatingip", "provider:hetzner:service", "live"],
96 timeout: 120_000,
97 },
98);
99
100test.provider.skipIf(!hasHetznerCreds)(
101 "replace when type changes",
102 (stack) =>
103 Effect.gen(function* () {
104 yield* stack.destroy();
105
106 const created = yield* stack.deploy(
107 Effect.gen(function* () {
108 return yield* Hetzner.FloatingIp("ReplaceIp", {
109 type: "ipv4",
110 homeLocation: "nbg1",
111 });
112 }),
113 );
114
115 expect(created.type).toEqual("ipv4");
116
117 const replaced = yield* stack.deploy(
118 Effect.gen(function* () {
119 return yield* Hetzner.FloatingIp("ReplaceIp", {
120 type: "ipv6",
121 homeLocation: "nbg1",
122 });
123 }),
124 );
125
126 expect(replaced.id).not.toEqual(created.id);
127 expect(replaced.type).toEqual("ipv6");
128 expect(replaced.ip).not.toEqual(created.ip);
129 expect(replaced.homeLocation).toEqual("nbg1");
130 expect(replaced.serverId).toBeNull();
131
132 const fetched = yield* floatingIps.getFloatingIp({
133 id: replaced.id,
134 });
135 expect(fetched.floating_ip.type).toEqual("ipv6");
136
137 const oldGone = yield* waitUntilGone(created.id);
138 expect(oldGone).toEqual("gone");
139
140 yield* stack.destroy();
141
142 const gone = yield* waitUntilGone(replaced.id);
143 expect(gone).toEqual("gone");
144 }).pipe(logLevel),
145 {
146 tags: ["provider:hetzner", "provider:hetzner:floatingip", "provider:hetzner:service", "live"],
147 timeout: 120_000,
148 },
149);
150
151test.provider.skipIf(!hasHetznerCreds)(
152 "list enumerates the deployed floating ip",
153 (stack) =>
154 Effect.gen(function* () {
155 yield* stack.destroy();
156
157 const deployed = yield* stack.deploy(
158 Effect.gen(function* () {
159 return yield* Hetzner.FloatingIp("ListIp", {
160 type: "ipv4",
161 homeLocation: "nbg1",
162 });
163 }),
164 );
165
166 const provider = yield* Provider.findProvider(Hetzner.FloatingIp);
167 const all = yield* provider.list();
168 const found = all.find((ip) => ip.id === deployed.id);
169 expect(found).toBeDefined();
170 expect(found?.type).toEqual("ipv4");
171 expect(found?.homeLocation).toEqual("nbg1");
172 expect(found?.ip).toEqual(deployed.ip);
173
174 yield* stack.destroy();
175
176 const gone = yield* waitUntilGone(deployed.id);
177 expect(gone).toEqual("gone");
178 }).pipe(logLevel),
179 {
180 tags: ["provider:hetzner", "provider:hetzner:floatingip", "provider:hetzner:service", "live"],
181 timeout: 120_000,
182 },
183);