1import { Query } from "@distilled.cloud/core/query";
2import { Railway as RailwayApi } from "@distilled.cloud/railway";
3import * as Railway from "@/Railway";
4import { suitePartition } from "../suiteProject.ts";
5import * as Test from "@/Test/Alchemy";
6import { expect } from "alchemy-test";
7import * as Effect from "effect/Effect";
8import { MinimumLogLevel } from "effect/References";
9import * as Schedule from "effect/Schedule";
10import * as HttpClient from "effect/http/HttpClient";
11import * as pathe from "pathe";
12import { cloneFixture } from "../../Cloudflare/Utils/Fixture.ts";
13import { expectUrlContains } from "../../Cloudflare/Utils/Http.ts";
14
15const { test } = Test.make({ providers: Railway.providers() });
16
17const logLevel = Effect.provideService(
18 MinimumLogLevel,
19 process.env.DEBUG ? "Debug" : "Info",
20);
21
22const fixtureDir = pathe.resolve(
23 import.meta.dirname,
24 "../../Cloudflare/Website/staticsite-fixture",
25);
26const tempRoot = pathe.resolve(import.meta.dirname, "../../../.tmp");
27
28const readService = Query.fn((id: string) => ({
29 deletedAt: RailwayApi.service({ id }).deletedAt,
30}));
31
32const waitUntilGone = (serviceId: string) =>
33 readService(serviceId).pipe(
34 Effect.map((service) =>
35 service.deletedAt != null ? ("gone" as const) : ("found" as const),
36 ),
37 Effect.catchTag("RailwayNotFound", () => Effect.succeed("gone" as const)),
38 Effect.repeat({
39 schedule: Schedule.spaced("1 second"),
40 until: (status) => status === "gone",
41 times: 10,
42 }),
43 );
44
45test.provider(
46 "StaticSite: build command + outdir, GET index",
47 (stack) =>
48 Effect.gen(function* () {
49 yield* stack.destroy();
50
51 const cwd = yield* cloneFixture(fixtureDir, {
52 prefix: "alchemy-staticsite-railway-",
53 tempRoot,
54 entries: ["src", "build.sh"],
55 });
56
57 const deployed = yield* stack.deploy(
58 Effect.gen(function* () {
59 const { project, environment } = yield* suitePartition;
60 const site = yield* Railway.Website.StaticSite("Blog", {
61 project,
62 environment,
63 cwd,
64 command: "bash build.sh",
65 shell: true,
66 outdir: "dist",
67 });
68 return { site };
69 }),
70 );
71
72 const url = deployed.site.url;
73 expect(url).toBeDefined();
74 expect(url).toMatch(/^https:\/\//);
75 expect(deployed.site.service).toBeDefined();
76
77 yield* expectUrlContains(`${url!}/`, "StaticSite fixture v1", {
78 timeout: "90 seconds",
79 label: "staticsite index",
80 });
81
82 const client = yield* HttpClient.HttpClient;
83 const health = yield* client.get(`${url!}/health`).pipe(
84 Effect.flatMap((res) =>
85 res.status === 200
86 ? res.text
87 : Effect.fail(new Error(`health returned ${res.status}`)),
88 ),
89 Effect.retry({
90 schedule: Schedule.exponential("500 millis"),
91 times: 6,
92 }),
93 );
94 expect(health).toContain("ok");
95
96 const serviceId = deployed.site.service!.serviceId;
97 yield* stack.destroy();
98 const gone = yield* waitUntilGone(serviceId);
99 expect(gone).toEqual("gone");
100 }).pipe(logLevel),
101 {
102 tags: [
103 "provider:railway",
104 "provider:railway:project",
105 "provider:railway:projectenvironment",
106 "provider:railway:service",
107 "provider:railway:website",
108 "live",
109 ],
110 timeout: 120_000,
111 },
112);