1import { RuntimeContext } from "alchemy";
2import * as Planetscale from "alchemy/Planetscale";
3import * as Test from "alchemy/Test/Alchemy";
4import { expect } from "alchemy-test";
5import * as Effect from "effect/Effect";
6import * as Redacted from "effect/Redacted";
7import { BetterAuth, Database } from "@/index.ts";
8import { applyMigrations } from "@/Migrate.ts";
9import { MySQL } from "@/MySQL.ts";
10
11const { test } = Test.make({ providers: Planetscale.providers() });
12
13const baseOptions = {
14 baseURL: "http://localhost:3000",
15 emailAndPassword: { enabled: true },
16 secret: "test-secret-test-secret-test-secret",
17} as const;
18
19test.provider(
20 "MySQL layer: migrate + auth roundtrip against PlanetScale",
21 (stack) =>
22 Effect.gen(function* () {
23 yield* stack.destroy();
24 const { database, password } = yield* stack.deploy(
25 Effect.gen(function* () {
26 const database = yield* Planetscale.MySQLDatabase("BetterAuthMySQL", {
27 clusterSize: "PS_10",
28 });
29 const password = yield* Planetscale.MySQLPassword(
30 "BetterAuthMySQLPassword",
31 { database, role: "admin" },
32 );
33 return { database, password };
34 }),
35 );
36
37 // PlanetScale requires TLS — mysql2 parses the `ssl` query param
38 const url =
39 `mysql://${password.username}:${Redacted.value(password.password)}` +
40 `@${password.host}/${database.name}` +
41 `?ssl=${encodeURIComponent('{"rejectUnauthorized":true}')}`;
42
43 const layer = MySQL(url);
44 const db = yield* Database.pipe(Effect.provide(layer));
45 expect(db.migrate).toBeDefined();
46
47 const first = yield* applyMigrations(db.migrate!, baseOptions);
48 expect(first.tablesCreated).toBeGreaterThan(0);
49 const second = yield* applyMigrations(db.migrate!, baseOptions);
50 expect(second.tablesCreated).toBe(0);
51
52 yield* Effect.scoped(
53 Effect.gen(function* () {
54 const auth = yield* BetterAuth(baseOptions);
55 const signUp = yield* auth.api.signUpEmail({
56 body: {
57 email: "planetscale@example.com",
58 password: "password1234",
59 name: "PlanetScale User",
60 },
61 });
62 expect(signUp.user.email).toBe("planetscale@example.com");
63 const signIn = yield* auth.api.signInEmail({
64 body: {
65 email: "planetscale@example.com",
66 password: "password1234",
67 },
68 });
69 expect(signIn.token).toBeDefined();
70 }).pipe(Effect.provide(layer)),
71 );
72
73 yield* stack.destroy();
74 }).pipe(Effect.provide(RuntimeContext.phantom)),
75 // Includes provisioning a fresh PlanetScale database.
76 {
77 tags: ["provider:planetscale", "provider:planetscale:mysql", "live"],
78 timeout: 240_000,
79 },
80);