1import * as Hetzner from "@/Hetzner";
2import * as Test from "@/Test/Alchemy";
3import { expect } from "alchemy-test";
4import * as Effect from "effect/Effect";
5import { MinimumLogLevel } from "effect/References";
6import * as Schedule from "effect/Schedule";
7import * as HttpClient from "effect/http/HttpClient";
8import {
9 materializeIsolatedProject,
10 removeIsolatedProject,
11} from "../IsolatedProject.ts";
12import IsolatedProjectApi, {
13 project,
14} from "./fixtures/isolated-project-service.ts";
15
16const { test } = Test.make({ providers: Hetzner.providers() });
17
18const logLevel = Effect.provideService(
19 MinimumLogLevel,
20 process.env.DEBUG ? "Debug" : "Info",
21);
22
23const hasHetznerCreds = !!process.env.HCLOUD_TOKEN;
24
25// Live proof that the Hetzner bun bootstrap boots when the service's `main`
26// lives in an isolated project (see test/IsolatedProject.ts) — the bundle
27// `cwd` resolves none of alchemy's dependencies, so the bootstrap's
28// `@effect/platform-bun` / `alchemy/*` imports must be bundled by the
29// virtual-entry plugin rather than found from the project root. With them
30// left external bun dies at module load under systemd and the port never
31// answers.
32test.provider.skipIf(!hasHetznerCreds)(
33 "service bundled from an isolated project serves HTTP",
34 (stack) =>
35 Effect.gen(function* () {
36 yield* materializeIsolatedProject(project);
37 yield* stack.destroy();
38
39 try {
40 const deployed = yield* stack.deploy(
41 Effect.gen(function* () {
42 return yield* IsolatedProjectApi;
43 }),
44 );
45 expect(deployed.url).toBeTruthy();
46
47 const health = yield* HttpClient.get(
48 new URL("/health", deployed.url!),
49 ).pipe(
50 Effect.flatMap((res) =>
51 res.status === 200
52 ? res.json
53 : Effect.fail(new Error(`/health returned ${res.status}`)),
54 ),
55 Effect.retry({ schedule: Schedule.spaced("2 seconds"), times: 20 }),
56 );
57 expect(health).toEqual({ ok: true });
58
59 yield* stack.destroy();
60 } finally {
61 yield* removeIsolatedProject(project);
62 }
63 }).pipe(logLevel),
64 {
65 tags: [
66 "provider:hetzner",
67 "provider:hetzner:server",
68 "provider:hetzner:service",
69 "live",
70 ],
71 timeout: 180_000,
72 exclusive: true,
73 },
74);