1import * as Hetzner from "@/Hetzner";
2import * as Test from "@/Test/Alchemy";
3import * as servers from "@distilled.cloud/hetzner/servers";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import { MinimumLogLevel } from "effect/References";
7import * as Schedule from "effect/Schedule";
8import * as pathe from "pathe";
9import { cloneFixture } from "../../Cloudflare/Utils/Fixture.ts";
10import { expectUrlContains } from "../../Cloudflare/Utils/Http.ts";
11
12const { test } = Test.make({ providers: Hetzner.providers() });
13
14const logLevel = Effect.provideService(
15 MinimumLogLevel,
16 process.env.DEBUG ? "Debug" : "Info",
17);
18
19const hasHetznerCreds = !!process.env.HCLOUD_TOKEN;
20
21const fixtureDir = pathe.resolve(
22 import.meta.dirname,
23 "../../AWS/Website/fixtures/sveltekit-app",
24);
25const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
26const fixtureEntries = [".gitignore", "package.json", "src", "static"];
27
28const waitUntilGone = (id: number) =>
29 servers.getServer({ id }).pipe(
30 Effect.as("found" as const),
31 Effect.catchTag("NotFound", () => Effect.succeed("gone" as const)),
32 Effect.repeat({
33 schedule: Schedule.spaced("1 second"),
34 until: (status) => status === "gone",
35 times: 10,
36 }),
37 );
38
39test.provider.skipIf(!hasHetznerCreds)(
40 "SvelteKit: deploy, GET /, destroy, gone",
41 (stack) =>
42 Effect.gen(function* () {
43 yield* stack.destroy();
44
45 const rootDir = yield* cloneFixture(fixtureDir, {
46 prefix: "alchemy-sveltekit-hetzner-",
47 tempRoot,
48 entries: fixtureEntries,
49 });
50
51 const deployed = yield* stack.deploy(
52 Effect.gen(function* () {
53 const site = yield* Hetzner.Website.SvelteKit("Web", {
54 rootDir,
55 memo: {
56 include: ["src/**", "static/**", "package.json"],
57 },
58 });
59 return { site };
60 }),
61 );
62
63 const url = deployed.site.url;
64 expect(url).toBeDefined();
65 expect(url).toMatch(/^http:\/\//);
66 expect(deployed.site.service).toBeDefined();
67 expect(deployed.site.server).toBeDefined();
68
69 yield* expectUrlContains(`${url!}/`, "SVELTEKIT_AWS_PAGE_MARKER", {
70 timeout: "90 seconds",
71 label: "home page",
72 });
73 yield* expectUrlContains(
74 `${url!}/api/hello?echo=roundtrip`,
75 "SVELTEKIT_AWS_API_MARKER",
76 {
77 timeout: "30 seconds",
78 label: "api route",
79 },
80 );
81
82 const serverId = deployed.site.server!.serverId;
83 yield* stack.destroy();
84 const gone = yield* waitUntilGone(serverId);
85 expect(gone).toEqual("gone");
86 }).pipe(
87 logLevel,
88 Effect.ensuring(stack.destroy().pipe(Effect.orDie)),
89 Effect.catchTag(["PreconditionFailed", "Forbidden"], (error) =>
90 Effect.logWarning(`skipping: Hetzner quota (${error._tag})`),
91 ),
92 ),
93 {
94 tags: [
95 "provider:hetzner",
96 "provider:hetzner:service",
97 "provider:hetzner:website",
98 "live",
99 ],
100 timeout: 180000,
101 },
102);