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 Stack from "../alchemy.run.ts";
9
10const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
11 providers: Railway.providers(),
12 state: Alchemy.localState(),
13 profile: process.env.ALCHEMY_PROFILE,
14});
15
16const fetchOk = (url: string) =>
17 Effect.gen(function* () {
18 const client = yield* HttpClient.HttpClient;
19 return yield* client.get(url).pipe(
20 Effect.flatMap((res) =>
21 res.status === 200
22 ? Effect.succeed(res)
23 : Effect.fail(new Error(`HTTP ${res.status}`)),
24 ),
25 Effect.retry({
26 schedule: Schedule.exponential("500 millis"),
27 times: 10,
28 }),
29 );
30 });
31
32const stack = beforeAll(deploy(Stack), { timeout: 180_000 });
33
34afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack), {
35 timeout: 120_000,
36});
37
38test(
39 "deploys http-echo on a Service and serves over up.railway.app",
40 Effect.gen(function* () {
41 const out = yield* stack;
42 expect(out.url).toBeString();
43 expect(out.url).toMatch(/^https:\/\/.+\.up\.railway\.app$/);
44 expect(out.projectId).toBeString();
45 expect(out.serviceId).toBeString();
46
47 const response = yield* fetchOk(out.url!);
48 expect(response.status).toBe(200);
49 }),
50 { timeout: 120_000 },
51);