| 1 | import { RuntimeContext } from "alchemy"; |
| 2 | import * as Planetscale from "alchemy/Planetscale"; |
| 3 | import * as Test from "alchemy/Test/Alchemy"; |
| 4 | import { expect } from "alchemy-test"; |
| 5 | import * as Effect from "effect/Effect"; |
| 6 | import * as Redacted from "effect/Redacted"; |
| 7 | import { BetterAuth, Database } from "@/index.ts"; |
| 8 | import { applyMigrations } from "@/Migrate.ts"; |
| 9 | import { MySQL } from "@/MySQL.ts"; |
| 10 | |
| 11 | const { test } = Test.make({ providers: Planetscale.providers() }); |
| 12 | |
| 13 | const baseOptions = { |
| 14 | baseURL: "http://localhost:3000", |
| 15 | emailAndPassword: { enabled: true }, |
| 16 | secret: "test-secret-test-secret-test-secret", |
| 17 | } as const; |
| 18 | |
| 19 | test.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 | ); |