1import * as Cloud from "alchemy/Railway";
2import * as Test from "alchemy/Test/Bun";
3import { expect } from "bun:test";
4import * as Effect from "effect/Effect";
5import * as FileSystem from "effect/FileSystem";
6import * as Path from "effect/Path";
7import * as Schedule from "effect/Schedule";
8
9const { test } = Test.make({ providers: Cloud.providers(), dev: true });
10
11test.provider(
12 "native vinext development serves routes, environment, and hot reload",
13 (stack) =>
14 Effect.gen(function* () {
15 yield* stack.destroy();
16 const fs = yield* FileSystem.FileSystem;
17 const path = yield* Path.Path;
18 const rootDir = yield* path.fromFileUrl(new URL("../", import.meta.url));
19 const { site } = yield* stack.deploy(
20 Effect.gen(function* () {
21 return {
22 site: yield* Cloud.Website.Vinext("Web", {
23 rootDir,
24 env: { GREETING: "Hello from vinext on Railway!" },
25 }),
26 };
27 }),
28 );
29 expect(site.url).toMatch(/^http:\/\/(localhost|127\.0\.0\.1):\d+/);
30 const url = String(site.url).replace(/\/+$/, "");
31 const home = yield* Test.getWhenReady(url).pipe(
32 Effect.flatMap((response) => response.text),
33 );
34 expect(home).toContain("Hello from vinext on Railway!");
35 const api = yield* Test.getWhenReady(`${url}/api/hello?name=Alchemy`);
36 expect(yield* api.json).toEqual({
37 name: "Alchemy",
38 greeting: "Hello from vinext on Railway!",
39 });
40 const robots = yield* Test.getWhenReady(`${url}/robots.txt`);
41 expect(yield* robots.text).toContain("User-agent:");
42 const isr = yield* Test.getWhenReady(`${url}/isr`);
43 expect(yield* isr.text).toContain("ISR");
44 const page = path.join(rootDir, "app/page.tsx");
45 const source = yield* fs.readFileString(page);
46 const marker = "Updated by the vinext development test";
47 yield* Effect.acquireUseRelease(
48 fs.writeFileString(
49 page,
50 source.replace("Hello from vinext on Railway!", marker),
51 ),
52 () =>
53 Test.getWhenReady(url).pipe(
54 Effect.flatMap((response) => response.text),
55 Effect.repeat({
56 schedule: Schedule.spaced("500 millis"),
57 times: 20,
58 until: (body) => body.includes(marker),
59 }),
60 Effect.tap((body) =>
61 Effect.sync(() => expect(body).toContain(marker)),
62 ),
63 ),
64 () => fs.writeFileString(page, source).pipe(Effect.orDie),
65 );
66 const restored = yield* Test.getWhenReady(url).pipe(
67 Effect.flatMap((response) => response.text),
68 Effect.repeat({
69 schedule: Schedule.spaced("500 millis"),
70 times: 20,
71 until: (body) => !body.includes(marker),
72 }),
73 );
74 expect(restored).toContain("Hello from vinext on Railway!");
75 expect(restored).not.toContain(marker);
76 yield* stack.destroy();
77 }),
78 { timeout: 120_000 },
79);