| 1 | import { |
| 2 | OAUTH_CLIENT_ID, |
| 3 | exchange, |
| 4 | usesCurrentClient, |
| 5 | } from "@/Planetscale/OAuthClient.ts"; |
| 6 | import { describe, expect, it } from "alchemy-test"; |
| 7 | import * as Effect from "effect/Effect"; |
| 8 | import * as Redacted from "effect/Redacted"; |
| 9 | import * as FetchHttpClient from "effect/http/FetchHttpClient"; |
| 10 | |
| 11 | describe( |
| 12 | "PlanetScale OAuth client credentials", |
| 13 | { tags: ["unit", "provider:planetscale", "local"] }, |
| 14 | () => { |
| 15 | it("maps the token response onto stored credentials", async () => { |
| 16 | // SAFETY: the test double implements the callable fetch contract; Bun's |
| 17 | // nonstandard static `preconnect` member is not used by FetchHttpClient. |
| 18 | const fetch = (async (_input, _init) => |
| 19 | new Response( |
| 20 | JSON.stringify({ |
| 21 | access_token: "access", |
| 22 | refresh_token: "refresh", |
| 23 | expires_in: 3600, |
| 24 | scope: "read write", |
| 25 | }), |
| 26 | { status: 200, headers: { "Content-Type": "application/json" } }, |
| 27 | )) as typeof globalThis.fetch; |
| 28 | |
| 29 | const before = Date.now(); |
| 30 | const credentials = await Effect.runPromise( |
| 31 | exchange("code").pipe( |
| 32 | Effect.provideService(FetchHttpClient.Fetch, fetch), |
| 33 | ), |
| 34 | ); |
| 35 | expect(credentials.type).toBe("oauth"); |
| 36 | expect(credentials.clientId).toBe(OAUTH_CLIENT_ID); |
| 37 | expect(Redacted.value(credentials.access)).toBe("access"); |
| 38 | expect(Redacted.value(credentials.refresh)).toBe("refresh"); |
| 39 | expect(credentials.scopes).toEqual(["read", "write"]); |
| 40 | expect(credentials.expires).toBeGreaterThanOrEqual( |
| 41 | before + 3_600_000 - 1_000, |
| 42 | ); |
| 43 | }); |
| 44 | |
| 45 | it("flags credentials issued to a rotated client id as stale", () => { |
| 46 | expect(usesCurrentClient({ clientId: OAUTH_CLIENT_ID })).toBe(true); |
| 47 | expect(usesCurrentClient({ clientId: "pscale_app_old" })).toBe(false); |
| 48 | // Credentials persisted before clientId was stored must read as stale so |
| 49 | // login falls back to a fresh browser authorization. |
| 50 | expect(usesCurrentClient({})).toBe(false); |
| 51 | }); |
| 52 | }, |
| 53 | ); |