Skip to content

Part 3: Testing

Write an integration test that deploys the stack from Part 2, calls the program, and checks the disk directly.

Terminal window
pnpm add -D https://pkg.alchemy.run/@alchemy.run/test/pr:2930:0cabab6

Test.make takes the same providers and state as your Stack:

test/integ.test.ts
import { expect } from "bun:test";
import * as Archil from "@alchemy.run/archil";
import * as Test from "@alchemy.run/test/Bun";
import * as Alchemy from "alchemy";
import * as Effect from "effect/Effect";
import * as HttpClient from "effect/http/HttpClient";
import * as HttpClientRequest from "effect/http/HttpClientRequest";
import * as Schedule from "effect/Schedule";
import Stack from "../alchemy.run.ts";
const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
providers: Archil.providers(),
state: Alchemy.localState(),
profile: process.env.ALCHEMY_PROFILE,
});

beforeAll(deploy(Stack)) deploys once for the whole file and hands back the stack’s outputs. afterAll destroys it unless NO_DESTROY is set:

test/integ.test.ts
const stack = beforeAll(deploy(Stack), { timeout: 300_000 });
afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(Stack), { timeout: 180_000 });

A fresh hostname, or a sandbox waking up, can take a few seconds to answer:

test/integ.test.ts
const send = (request: HttpClientRequest.HttpClientRequest) =>
Effect.gen(function* () {
const client = yield* HttpClient.HttpClient;
return yield* client.execute(request).pipe(
Effect.flatMap((res) =>
res.status >= 500 ? Effect.fail(new Error(`HTTP ${res.status}`)) : Effect.succeed(res),
),
Effect.retry({ schedule: Schedule.spaced("2 seconds"), times: 20 }),
);
});
test/integ.test.ts
test(
"GET of a missing file is a 404",
Effect.gen(function* () {
const { url } = yield* stack;
const res = yield* send(HttpClientRequest.get(`${url}/missing.txt`));
expect(res.status).toBe(404);
}),
{ timeout: 180_000 },
);

test.provider runs with your Archil credentials, so the test can read the disk out of band and confirm the file is really there:

test/integ.test.ts
import * as Objects from "@distilled.cloud/archil/Objects";
test.provider(
"PUT writes a file to the disk and GET reads it back",
() =>
Effect.gen(function* () {
const { url, diskId } = yield* stack;
const key = `notes/${crypto.randomUUID()}.txt`;
yield* send(
HttpClientRequest.put(`${url}/${key}`).pipe(HttpClientRequest.bodyText("on the disk")),
);
const text = yield* Objects.getObjectText({ disk: String(diskId) }, key).pipe(
Effect.provide(Objects.ObjectCredentialsFromApiKey),
);
expect(text).toBe("on the disk");
}),
{ timeout: 180_000 },
);
Terminal window
ALCHEMY_PROFILE=default bun test

Set NO_DESTROY=1 to keep the stack between runs while you iterate.

The test deploys the real stack, calls it over HTTP, and checks Archil directly. In Part 4 you’ll deploy separate copies with stages.