128 lines · 5.8 KB · TypeScript
RawDownload
// `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<void>;}> => {  const sockets = new Set<Socket>();  const server: Server = createServer((socket) => {    sockets.add(socket);    socket.on("close", () => sockets.delete(socket));  });  await new Promise<void>((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<void>((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<unknown> => {  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<Result.Result<never, UserStoreError>> =>  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<Result.Result<never, UserStoreError>>; 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);});