1import * as Cloudflare from "alchemy/Cloudflare";
2import * as Test from "alchemy/Test/Bun";
3import { expect } from "bun:test";
4import * as Effect from "effect/Effect";
5import * as Schedule from "effect/Schedule";
6import * as HttpBody from "effect/http/HttpBody";
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: Cloudflare.providers(),
13 state: Cloudflare.state(),
14 // `STAGE=pr-147 bun test` targets the stage `alchemy deploy --stage pr-147`
15 // created; otherwise the harness default (`test_$USER`) is used.
16 ...(process.env.STAGE ? { stage: process.env.STAGE } : {}),
17});
18
19// A no-op when the stage is already deployed.
20const stack = beforeAll(deploy(Stack));
21
22afterAll.skipIf(!process.env.CI)(destroy(Stack));
23
24test(
25 "GET /photos renders the gallery",
26 Effect.gen(function* () {
27 const { url } = yield* stack;
28
29 // fresh workers.dev URLs can take a moment to start serving
30 const res = yield* HttpClient.get(`${url}/photos`).pipe(
31 Effect.filterOrFail((res) => res.status === 200),
32 Effect.retry({ schedule: Schedule.spaced("250 millis"), times: 20 }),
33 );
34 const body = (yield* res.json) as { photos: string[] };
35 expect(Array.isArray(body.photos)).toBe(true);
36 }),
37);
38
39test(
40 "uploads a photo to R2",
41 Effect.gen(function* () {
42 const { url } = yield* stack;
43
44 const put = yield* HttpClient.put(`${url}/photos/cat.jpg`, {
45 body: HttpBody.text("fake-jpeg-bytes"),
46 });
47 expect(put.status).toBe(201);
48
49 const res = yield* HttpClient.get(`${url}/photos`);
50 const body = (yield* res.json) as { photos: string[] };
51 expect(body.photos).toContain("cat.jpg");
52 }),
53);
54
55test(
56 "session survives a reload",
57 Effect.gen(function* () {
58 const { url } = yield* stack;
59
60 const first = (yield* (yield* HttpClient.get(`${url}/session`)).json) as {
61 id: string;
62 };
63 const reload = yield* HttpClient.execute(
64 HttpClientRequest.get(`${url}/session`).pipe(
65 HttpClientRequest.setHeader("x-session-id", first.id),
66 ),
67 );
68 const second = (yield* reload.json) as { value: string | null };
69 expect(second.value).toBe(first.id);
70 }),
71);