| 1 | import * as Cloudflare from "alchemy/Cloudflare"; |
| 2 | import * as Test from "alchemy/Test/Bun"; |
| 3 | import { expect } from "bun:test"; |
| 4 | import * as Effect from "effect/Effect"; |
| 5 | import * as Schedule from "effect/Schedule"; |
| 6 | import * as HttpBody from "effect/http/HttpBody"; |
| 7 | import * as HttpClient from "effect/http/HttpClient"; |
| 8 | import * as HttpClientRequest from "effect/http/HttpClientRequest"; |
| 9 | import Stack from "../alchemy.run.ts"; |
| 10 | |
| 11 | const { 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. |
| 20 | const stack = beforeAll(deploy(Stack)); |
| 21 | |
| 22 | afterAll.skipIf(!process.env.CI)(destroy(Stack)); |
| 23 | |
| 24 | test( |
| 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 | |
| 39 | test( |
| 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 | |
| 55 | test( |
| 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 | ); |