import { expect, it } from "@effect/vitest"; import { Data, Effect, Layer } from "effect"; import { WorkOSClient, type WorkOSClientService } from "../auth/workos"; import { emailResolver } from "./admin-users-api"; // Cloud's REVERSE directory lookup: email -> the WorkOS `user_...` id that the // subject table records in `external_id`. // // WHY THIS IS BUILT ON THE MEMBERSHIP LIST. WorkOS's SDK exposes // `listUsers({ email })`, but the pinned `@executor-js/emulate` WorkOS emulator // serves no list-users route at all — under `/user_management` it has only // `users/:id`, `organization_memberships`, the auth/session routes, and // invitations. A resolver built on `listUsers` would therefore 404 in every // cloud e2e run. So the lookup reuses the SAME membership read the forward // identity join already makes, which the emulator does serve, and scans it in // memory (bounded by org size). // // The membership list is also the authority on who belongs to THIS org, so the // resolver structurally cannot return a user from another tenant — something a // bare `listUsers({ email })` could do. const ORG = "org_placeholder"; /** The failure a real WorkOS read raises for one user. Typed rather than a bare * `Error`, so the resolver's skip-and-continue behaviour is exercised against * the shape the client actually fails with. */ class WorkOSUnavailable extends Data.TaggedError("WorkOSUnavailable")<{ readonly userId: string; }> {} /** Members of the org, with the casing WorkOS would have stored. WorkOS * preserves whatever casing a user was created with (and the emulator * compares byte-exact), so the mixed-case entry is the realistic case. */ const DIRECTORY: Record = { user_ada: { email: "Ada@Placeholder.test" }, user_grace: { email: "grace@placeholder.test" }, // A member the directory cannot name: `getUser` succeeds but carries no // email. It must never match, and must not break the scan for others. user_nameless: { email: null }, }; const stubWorkOS = (calls: string[]) => Layer.succeed( WorkOSClient, new Proxy({} as WorkOSClientService, { get: (_target, prop) => { if (prop === "listOrgMembers") { return (organizationId: string) => { calls.push(`listOrgMembers:${organizationId}`); return Effect.succeed({ data: Object.keys(DIRECTORY).map((userId) => ({ userId, organizationId })), }); }; } if (prop === "getUser") { return (userId: string) => { calls.push(`getUser:${userId}`); const user = DIRECTORY[userId]; if (!user) return Effect.die(`unexpected user ${userId}`); return Effect.succeed(user); }; } // A resolver that reaches for any other WorkOS call — notably a // `listUsers` the emulator cannot serve — is the failure this catches. return () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`); }, }), ); const resolve = (email: string, calls: string[]) => Effect.gen(function* () { const context = yield* Effect.context(); return yield* emailResolver(ORG, context)(email); }).pipe(Effect.provide(stubWorkOS(calls))); it.effect("resolves an email to the member's WorkOS user id", () => Effect.gen(function* () { const calls: string[] = []; // The seam hands the resolver an already-normalized address; the STORED // value is mixed-case, so this pins that the directory side is normalized // too. Without that, `Ada@Placeholder.test` would never match. expect(yield* resolve("ada@placeholder.test", calls)).toBe("user_ada"); expect(calls[0]).toBe(`listOrgMembers:${ORG}`); }), ); it.effect("returns null for an email no member holds", () => Effect.gen(function* () { const calls: string[] = []; expect(yield* resolve("nobody@placeholder.test", calls)).toBeNull(); // It looked at everyone before saying no, and a member with no email // neither matched nor derailed the scan. expect(calls).toContain("getUser:user_nameless"); }), ); it.effect("stops fetching once it finds the match", () => Effect.gen(function* () { const calls: string[] = []; // `user_ada` is first in the member list, so a short-circuiting scan must // never reach the members behind it. This is what keeps the in-memory scan // acceptable at the sizes this plane serves. expect(yield* resolve("ada@placeholder.test", calls)).toBe("user_ada"); expect(calls).not.toContain("getUser:user_grace"); expect(calls).not.toContain("getUser:user_nameless"); }), ); it.effect("does not let one unreadable member hide the real match", () => Effect.gen(function* () { const calls: string[] = []; const failing = Layer.succeed( WorkOSClient, new Proxy({} as WorkOSClientService, { get: (_target, prop) => { if (prop === "listOrgMembers") { return () => Effect.succeed({ data: [{ userId: "user_broken" }, { userId: "user_grace" }], }); } if (prop === "getUser") { return (userId: string) => { calls.push(userId); return userId === "user_broken" ? Effect.fail(new WorkOSUnavailable({ userId })) : Effect.succeed(DIRECTORY[userId]!); }; } return () => Effect.die(`unexpected WorkOSClient.${String(prop)} call`); }, }), ); const resolved = yield* Effect.gen(function* () { const context = yield* Effect.context(); return yield* emailResolver(ORG, context)("grace@placeholder.test"); }).pipe(Effect.provide(failing)); expect(resolved, "an unreadable member is skipped, not fatal").toBe("user_grace"); expect(calls).toEqual(["user_broken", "user_grace"]); }), );