1import * as Cloudflare from "@/Cloudflare";
2import * as Neon from "@/Neon";
3import * as Test from "@/Test/Alchemy";
4import { describe, expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Layer from "effect/Layer";
7import { MinimumLogLevel } from "effect/References";
8import { expectDatabaseReachable } from "./fixtures/sqlreach/expect.ts";
9import NeonHostStack from "./fixtures/neonhost/stack.ts";
10
11const { test, beforeAll, afterAll, deploy, destroy } = Test.make({
12 providers: Layer.merge(Cloudflare.providers(), Neon.providers()),
13 state: Cloudflare.state(),
14 dev: true,
15});
16
17const logLevel = Effect.provideService(
18 MinimumLogLevel,
19 process.env.DEBUG ? "Debug" : "Info",
20);
21
22const HOOK_TIMEOUT = 300_000;
23const TEST_TIMEOUT = 240_000;
24
25/**
26 * Neon is mode-agnostic: `alchemy dev` still hands the container a cloud
27 * `*.neon.tech` URL. The loopback rewrite must not touch it, and with
28 * `enableInternet` the container must be able to dial it — the same
29 * #1334 "container reaches a SQL database" path as Prisma, minus a local
30 * emulator.
31 */
32describe(
33 "local container reaches Neon Postgres",
34 {
35 tags: [
36 "provider:cloudflare",
37 "provider:cloudflare:container",
38 "provider:cloudflare:worker",
39 "provider:neon",
40 "provider:neon:project",
41 "live",
42 ],
43 },
44 () => {
45 const stack = beforeAll(deploy(NeonHostStack), { timeout: HOOK_TIMEOUT });
46 afterAll.skipIf(!!process.env.NO_DESTROY)(destroy(NeonHostStack), {
47 timeout: HOOK_TIMEOUT,
48 });
49
50 test(
51 "container DATABASE_URL keeps the Neon host and reaches it",
52 Effect.gen(function* () {
53 const { url } = yield* stack;
54 yield* expectDatabaseReachable(url, (hostname) => {
55 expect(hostname).not.toBe("localhost");
56 expect(hostname).not.toBe("127.0.0.1");
57 expect(hostname).not.toContain("host.docker.localhost");
58 expect(hostname).toMatch(/neon\.tech$/);
59 });
60 }).pipe(logLevel),
61 { timeout: TEST_TIMEOUT },
62 );
63 },
64);