1import * as Alchemy from "alchemy";
2import * as Railway from "alchemy/Railway";
3import * as Test from "alchemy/Test/Bun";
4import { expect } from "bun:test";
5import * as Effect from "effect/Effect";
6import * as Schedule from "effect/Schedule";
7import * as HttpClient from "effect/http/HttpClient";
8import * as HttpClientRequest from "effect/http/HttpClientRequest";
9import Stack from "../alchemy.run.ts";
10
11const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
12 providers: Railway.providers(),
13 state: Alchemy.localState(),
14 profile: process.env.ALCHEMY_PROFILE,
15});
16
17const executeOk = (request: HttpClientRequest.HttpClientRequest) =>
18 Effect.gen(function* () {
19 const client = yield* HttpClient.HttpClient;
20 return yield* client.execute(request).pipe(
21 Effect.flatMap((res) =>
22 res.status === 200 || res.status === 201
23 ? Effect.succeed(res)
24 : Effect.fail(new Error(`HTTP ${res.status}`)),
25 ),
26 Effect.retry({
27 schedule: Schedule.spaced("2 seconds"),
28 times: 30,
29 }),
30 );
31 });
32
33const stack = beforeAll(deploy(Stack), { timeout: 240_000 });
34
35afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack), {
36 timeout: 180_000,
37});
38
39test(
40 "SPA renders the Notes heading",
41 Effect.gen(function* () {
42 const { url } = yield* stack;
43 if (!url) throw new Error("expected the site to expose a url");
44 const base = url.replace(/\/+$/, "");
45 const response = yield* executeOk(HttpClientRequest.get(`${base}/`));
46 const body = yield* response.text;
47 expect(body).toContain("Notes — Railway");
48 }),
49 { timeout: 180_000 },
50);
51
52test(
53 "API persists a note in Postgres",
54 Effect.gen(function* () {
55 const { apiUrl } = yield* stack;
56 if (!apiUrl) throw new Error("expected the API url");
57 const base = String(apiUrl).replace(/\/+$/, "");
58 const marker = `railway-note-${crypto.randomUUID()}`;
59 yield* executeOk(
60 HttpClientRequest.post(`${base}/notes`).pipe(
61 HttpClientRequest.bodyJsonUnsafe({ body: marker }),
62 ),
63 );
64 const listed = yield* executeOk(HttpClientRequest.get(`${base}/notes`));
65 const payload = (yield* listed.json) as {
66 notes: Array<{ body: string }>;
67 };
68 expect(payload.notes.some((note) => note.body === marker)).toBe(true);
69 }),
70 { timeout: 180_000 },
71);