// `UserStoreError` is the public failure of every cloud user-store call, and it // used to carry NOTHING: no operation, no reason, no message. Sentry grouped // every store failure — a connect timeout, a missing table, a constraint // violation — into one titleless issue, and the only cause detail (the // pretty-printed Effect cause stuffed into a Sentry `extra`) is scrubbed // server-side, so the issue was undiagnosable from Sentry alone. // // This pins the two safe classification fields it must carry instead: // `operation` (already in hand at the call site) and `reason` (classified from // the driver cause the way `statusFromWorkOSCause` classifies WorkOS causes). import { createServer, type Server, type Socket } from "node:net"; import { describe, expect, it } from "@effect/vitest"; import { Effect, Layer, Result } from "effect"; import postgres from "postgres"; import { UserStoreService } from "./context"; import { ServiceAdapterError, userStoreReasonFromCause, type UserStoreError } from "./errors"; import { DbService } from "../db/db"; // A socket that completes the TCP handshake and then says nothing — exactly // what a wedged Hyperdrive/Postgres endpoint looks like to postgres.js, and the // only way to obtain the driver's REAL connect-timeout error object rather than // a hand-written imitation of it. const blackHolePort = async (): Promise<{ readonly port: number; readonly close: () => Promise; }> => { const sockets = new Set(); const server: Server = createServer((socket) => { sockets.add(socket); socket.on("close", () => sockets.delete(socket)); }); await new Promise((resolve) => server.listen(0, "127.0.0.1", resolve)); const address = server.address(); // oxlint-disable-next-line executor/no-error-constructor, executor/no-try-catch-or-throw -- boundary: the fixture cannot run without a bound port if (address === null || typeof address === "string") throw new Error("no port"); return { port: address.port, close: () => new Promise((resolve) => { // The timed-out client leaves its half-open socket attached; without // dropping it first, `close` waits for a peer that will never speak. for (const socket of sockets) socket.destroy(); server.close(() => resolve()); }), }; }; const realConnectTimeoutError = async (): Promise => { const hole = await blackHolePort(); const sql = postgres(`postgresql://postgres:postgres@127.0.0.1:${hole.port}/postgres`, { max: 1, connect_timeout: 1, fetch_types: false, onnotice: () => undefined, }); // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: capturing the driver's own thrown error IS the fixture try { await sql`select 1`; // oxlint-disable-next-line executor/no-error-constructor, executor/no-try-catch-or-throw -- boundary: the fixture is unusable if the socket answered throw new Error("expected the connect to time out"); } catch (error) { return error; } finally { // oxlint-disable-next-line executor/no-promise-catch -- boundary: best-effort teardown of a connection that never opened await sql.end({ timeout: 0 }).catch(() => undefined); await hole.close(); } }; // Drizzle re-throws driver failures wrapped in its own error with the failing // SQL in the message and the driver error in `.cause` — the shape production // actually reports (`Failed query: select … -> write CONNECT_TIMEOUT …`). const wrappedLikeDrizzle = (cause: unknown): Error => // oxlint-disable-next-line executor/no-error-constructor -- boundary: reproducing the driver wrapper shape the store really fails with Object.assign(new Error('Failed query: select "id" from "organizations" where "id" = $1'), { cause, }); const stubDb = Layer.succeed(DbService)({ db: {} as never }); const failingStoreCall = ( operation: string, failure: unknown, ): Effect.Effect> => Effect.gen(function* () { const users = yield* UserStoreService; // oxlint-disable-next-line executor/no-promise-reject -- boundary: the store adapter lifts a REJECTING promise; rejecting is the fixture return yield* users.use(operation, () => Promise.reject(failure)); }).pipe( Effect.provide(UserStoreService.Live.pipe(Layer.provide(stubDb))), Effect.result, ) as Effect.Effect>; describe("UserStoreError classification", () => { it("classifies the driver cause chain", async () => { const driverError = await realConnectTimeoutError(); expect(userStoreReasonFromCause(wrappedLikeDrizzle(driverError))).toBe("connect_timeout"); expect( userStoreReasonFromCause(new ServiceAdapterError({ cause: wrappedLikeDrizzle(driverError) })), ).toBe("connect_timeout"); expect( userStoreReasonFromCause( // oxlint-disable-next-line executor/no-error-constructor -- boundary: a SQLSTATE failure shape Object.assign(new Error('relation "organizations" does not exist'), { code: "42P01" }), ), ).toBe("query"); // oxlint-disable-next-line executor/no-error-constructor -- boundary: a failure with nothing to classify expect(userStoreReasonFromCause(new Error("something else"))).toBe("unknown"); }, 20_000); it("carries operation, reason and a stable message on the public error", async () => { const driverError = await realConnectTimeoutError(); const result = await Effect.runPromise( failingStoreCall("getOrganization", wrappedLikeDrizzle(driverError)), ); expect(Result.isFailure(result)).toBe(true); if (!Result.isFailure(result)) return; expect(result.failure.operation).toBe("getOrganization"); expect(result.failure.reason).toBe("connect_timeout"); expect(result.failure.message).toContain("getOrganization"); expect(result.failure.message).toContain("connect_timeout"); }, 20_000); });