// ---------------------------------------------------------------------------
// The cloud membership mirror: `WorkOsMirror` (writes) + the cloud
// `MemberDirectory` (reads), against the real PGlite Postgres every cloud
// unit test runs on (scripts/test-globalsetup.ts), through the same
// `DbService.Live` the request path uses.
//
// What this pins:
// - an older WorkOS payload never overwrites a newer row (replay-safe)
// - a delete tombstones the row (inactive, `deleted_at` set) by IDENTITY:
// no payload naming the deleted membership id ever reactivates it,
// however it is stamped — not a stale one, not one stamped after the
// removal — while a replacement under a NEW id takes the row over live;
// every default read treats the tombstone as no membership
// - a membership WorkOS merely deactivated (inactive, no `deleted_at`)
// reactivates under the same id like any other update
// - a delete of a membership or user the mirror has not seen yet leaves
// the tombstone behind, so the backfill's older payload cannot insert
// the row live afterwards
// - a delete of a membership id the row does NOT hold (the member's row
// still carries the id it was replaced from) is recorded all the same,
// so a later, newer payload of the deleted id cannot take the row over
// - a deleted user takes no membership at all, however the payload is
// stamped: the account tombstone refuses the insert by identity — and a
// membership write racing the deletion waits for its commit and sees
// the tombstone, never inserting a live membership for a deleted user
// - a delete with no WorkOS instant keeps the row's own WorkOS stamp, so
// a replacement membership WorkOS created meanwhile is not refused,
// while the removed membership's own payload still is
// - the cursor advances only by compare-and-set (one owner per stream),
// and a page that loses the CAS writes nothing
// - a backfill scan is applied only if its listing is newer than the one
// already applied to the organization (one owner per listing instant),
// so an older listing cannot insert a membership the newer one lacked;
// a deleted organization takes no scan
// - a membership payload stamped before the organization's last scan is
// refused (a login list fetched before a revocation the scan already
// applied cannot reinstate it); one stamped at or after it is written —
// and a write racing a scan waits for the scan's commit and sees its
// mark, so it cannot insert a membership the scan just proved revoked
// - the events replay boundary is recorded once and never advanced
// - `members` searches email AND name case-insensitively, pages stably
// - `findByEmail` ignores the casing WorkOS stored
// - `membershipById` is org-scoped: another org's id resolves to null
// - a membership arriving before its user still holds (FK via ensureAccount)
// ---------------------------------------------------------------------------
import { describe, expect, it } from "@effect/vitest";
import { eq, sql } from "drizzle-orm";
import { Context, Deferred, Duration, Effect, Fiber, Layer, Option } from "effect";
import { MemberDirectory } from "@executor-js/api/server";
import { DbService, makeDbLayer } from "../db/db";
import { accounts, organizations } from "../db/schema";
import { cloudMemberDirectoryLayer } from "./member-directory";
import { UserStoreService } from "./context";
import {
WorkOsMirror,
WorkOsMirrorWrite,
type WorkOsMirrorMembership,
type WorkOsMirrorUser,
} from "./workos-mirror";
import { makeWorkOsMirrorStore } from "./workos-mirror-store";
import {
MIRROR_RECONCILER_LAG_BUDGET,
MirrorReadinessState,
mirrorReadinessFrom,
readMirrorReadiness,
} from "./mirror-readiness-store";
const DbLive = DbService.Live;
const Services = Layer.mergeAll(
WorkOsMirror.Live,
cloudMemberDirectoryLayer,
UserStoreService.Live,
).pipe(Layer.provideMerge(DbLive));
const run = (
body: Effect.Effect,
) => Effect.runPromise(body.pipe(Effect.provide(Services), Effect.scoped));
/** The events row is instance-wide: drop it so a test starts as a never-backfilled database. */
const clearEventsRow = Effect.flatMap(DbService.asEffect(), ({ db }) =>
Effect.promise(() => db.execute(sql`delete from workos_sync where id = 'events'`)),
);
const at = (iso: string) => new Date(iso);
const T1 = at("2026-01-01T00:00:00.000Z");
const T2 = at("2026-01-02T00:00:00.000Z");
const T3 = at("2026-01-03T00:00:00.000Z");
const T4 = at("2026-01-04T00:00:00.000Z");
// Every test mints its own org so the shared test database never couples
// them; ids are synthetic placeholders, never real identities.
const freshOrg = () =>
Effect.gen(function* () {
const id = `org_${crypto.randomUUID().replaceAll("-", "")}`;
const store = yield* UserStoreService;
yield* store.use("upsertOrganization", (s) =>
s.upsertOrganization({ id, name: "Mirror Org", updatedAt: T1 }),
);
return id;
});
const user = (id: string, overrides: Partial = {}): WorkOsMirrorUser => ({
id,
email: `${id}@placeholder.test`,
firstName: null,
lastName: null,
avatarUrl: null,
lastSignInAt: null,
updatedAt: T1,
...overrides,
});
const membership = (
organizationId: string,
accountId: string,
overrides: Partial = {},
): WorkOsMirrorMembership => ({
id: `om_${accountId}_${organizationId}`,
accountId,
organizationId,
role: "member",
status: "active",
updatedAt: T1,
...overrides,
});
describe("WorkOsMirror upserts", () => {
it("ignores a user payload older than the stored row, accepts a newer one", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
yield* mirror.upsertMembership(membership(org, id));
const first = yield* mirror.upsertUser(user(id, { firstName: "Ada", updatedAt: T2 }));
const stale = yield* mirror.upsertUser(user(id, { firstName: "Stale", updatedAt: T1 }));
const afterStale = yield* directory.membership(id, org);
const newer = yield* mirror.upsertUser(user(id, { firstName: "Newer", updatedAt: T3 }));
const afterNewer = yield* directory.membership(id, org);
return { first, stale, newer, afterStale, afterNewer };
}),
);
expect(result.first).toBe(true);
expect(result.stale, "an older payload is reported as not written").toBe(false);
expect(result.afterStale?.name, "and left the newer row untouched").toBe("Ada");
expect(result.newer).toBe(true);
expect(result.afterNewer?.name).toBe("Newer");
});
it("ignores a membership payload older than the stored row", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
yield* mirror.upsertUser(user(id));
yield* mirror.upsertMembership(membership(org, id, { role: "admin", updatedAt: T2 }));
const stale = yield* mirror.upsertMembership(
membership(org, id, {
role: "member",
status: "inactive",
updatedAt: T1,
}),
);
const row = yield* directory.membership(id, org);
const equal = yield* mirror.upsertMembership(
membership(org, id, { role: "member", updatedAt: T2 }),
);
const afterEqual = yield* directory.membership(id, org);
return { stale, row, equal, afterEqual };
}),
);
expect(result.stale).toBe(false);
expect(result.row?.role).toBe("admin");
expect(result.row?.status).toBe("active");
// Equal timestamps are accepted: the feeders replay the same payload and
// must converge, not stall.
expect(result.equal).toBe(true);
expect(result.afterEqual?.role).toBe("member");
});
it("mints the account row when a membership arrives before its user", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const written = yield* mirror.upsertMembership(membership(org, id));
const bare = yield* directory.membership(id, org);
// The bare row has no timestamp, so the first user payload — even an
// "old" one — fills it.
yield* mirror.upsertUser(user(id, { firstName: "Late", updatedAt: T1 }));
const filled = yield* directory.membership(id, org);
return { written, bare, filled };
}),
);
expect(result.written).toBe(true);
expect(result.bare).not.toBeNull();
expect(result.bare?.email).toBeNull();
expect(result.filled?.name).toBe("Late");
});
it("tombstones a deleted membership by identity: no payload of that id resurrects it, a replacement under a new id does", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const membershipId = `om_${id}_${org}`;
yield* mirror.upsertUser(user(id));
yield* mirror.upsertMembership(membership(org, id, { id: membershipId, updatedAt: T1 }));
const ref = { id: membershipId, accountId: id, organizationId: org };
const removed = yield* mirror.deleteMembership(ref, T2);
const removedAgain = yield* mirror.deleteMembership(ref, T2);
const byDefault = yield* directory.membership(id, org);
const listed = yield* directory.members(org);
const asInactive = yield* directory.membership(id, org, ["inactive"]);
// A feeder that fetched the membership BEFORE the deletion (login,
// backfill) writes it after: the guard refuses it.
const stale = yield* mirror.upsertMembership(
membership(org, id, { id: membershipId, updatedAt: T1 }),
);
const afterStale = yield* directory.membership(id, org, ["inactive"]);
// An active payload stamped the SAME instant as the deletion: the
// tombstone wins, a deletion at T is never undone by a payload at T.
const equal = yield* mirror.upsertMembership(
membership(org, id, { id: membershipId, updatedAt: T2 }),
);
const afterEqual = yield* directory.membership(id, org, ["inactive"]);
// A role change issued before the removal and delivered after it (a
// stalled login list, a lagging feeder), stamped NEWER than anything
// the row holds — the payload a timestamp guard would let through.
// Same id: the membership is deleted, it never returns.
const newerSameId = yield* mirror.upsertMembership(
membership(org, id, {
id: membershipId,
role: "admin",
updatedAt: T3,
}),
);
const afterNewerSameId = yield* directory.membership(id, org, ["inactive"]);
// The member re-added in WorkOS: a payload newer than the deletion,
// under a new membership id (a deleted id is never reused).
const readded = yield* mirror.upsertMembership(
membership(org, id, { id: `${membershipId}_2`, updatedAt: T3 }),
);
const afterReadd = yield* directory.membership(id, org);
// The OLD membership's deletion, replayed after the re-add: the
// newer row, under its new id, stands.
const lateDelete = yield* mirror.deleteMembership(ref, T2);
const afterLateDelete = yield* directory.membership(id, org);
// The same deletion stamped AFTER the replacement (a removal Executor
// made whose clock was read once WorkOS had answered, by which time
// the member had been re-added): the row is another membership, so
// the timestamp does not make it deletable.
const lateDeleteNewerStamp = yield* mirror.deleteMembership(ref, T4);
const afterLateDeleteNewerStamp = yield* directory.membership(id, org);
return {
removed,
removedAgain,
byDefault,
listed,
asInactive,
stale,
afterStale,
equal,
afterEqual,
newerSameId,
afterNewerSameId,
readded,
afterReadd,
lateDelete,
afterLateDelete,
lateDeleteNewerStamp,
afterLateDeleteNewerStamp,
};
}),
);
expect(result.removed).toBe(true);
expect(result.removedAgain, "a replayed delete changes nothing").toBe(false);
expect(result.byDefault, "a tombstone reads as no membership").toBeNull();
expect(result.listed, "and is not listed").toEqual([]);
expect(result.asInactive, "but is still there when asked for").toMatchObject({
status: "inactive",
lastActiveAt: null,
});
expect(result.stale, "an upsert older than the deletion is refused").toBe(false);
expect(result.afterStale?.status).toBe("inactive");
expect(result.equal, "an upsert stamped AT the deletion is refused too").toBe(false);
expect(result.afterEqual?.status).toBe("inactive");
expect(
result.newerSameId,
"a payload of the deleted id stamped AFTER the removal is refused: identity, not time",
).toBe(false);
expect(result.afterNewerSameId).toMatchObject({
status: "inactive",
role: "member",
});
expect(result.readded, "a replacement under a new id reactivates").toBe(true);
expect(result.afterReadd?.status).toBe("active");
expect(result.lateDelete, "a replayed deletion of the OLD id is refused").toBe(false);
expect(result.afterLateDelete?.status).toBe("active");
expect(
result.lateDeleteNewerStamp,
"a deletion of the OLD id stamped after the replacement is refused too: identity, not time",
).toBe(false);
expect(result.afterLateDeleteNewerStamp).toMatchObject({
status: "active",
membershipId: `om_${result.afterLateDeleteNewerStamp?.accountId}_${result.afterLateDeleteNewerStamp?.organizationId}_2`,
});
});
it("keeps the row's own WorkOS stamp on a delete with no instant, so a replacement created meanwhile is accepted and the removed payload is not", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const membershipId = `om_${id}_${org}`;
const ref = { id: membershipId, accountId: id, organizationId: org };
yield* mirror.upsertUser(user(id));
yield* mirror.upsertMembership(membership(org, id, { id: membershipId, updatedAt: T1 }));
// Executor removes the member holding no WorkOS instant for it, so
// the tombstone keeps T1, the last state WorkOS reported for it —
// never the local clock, which is long past T2 here.
const removed = yield* mirror.deleteMembership(ref, null);
const removedAgain = yield* mirror.deleteMembership(ref, null);
const tombstone = yield* directory.membership(id, org, ["inactive"]);
// A login that fetched the membership before the removal: refused.
const stale = yield* mirror.upsertMembership(
membership(org, id, { id: membershipId, updatedAt: T1 }),
);
// The member re-added in WorkOS while the removal was in flight,
// under a new id and stamped before any local clock could have
// stamped the tombstone: accepted.
const replaced = yield* mirror.upsertMembership(
membership(org, id, { id: `${membershipId}_2`, updatedAt: T2 }),
);
const afterReplace = yield* directory.membership(id, org);
// A tombstone minted for a row the mirror never held has no stamp
// to keep; it is still a tombstone.
const other = `user_${crypto.randomUUID()}`;
const minted = yield* mirror.deleteMembership(
{ id: `om_${other}_${org}`, accountId: other, organizationId: org },
null,
);
const mintedRow = yield* directory.membership(other, org, ["inactive"]);
return {
membershipId,
removed,
removedAgain,
tombstone,
stale,
replaced,
afterReplace,
minted,
mintedRow,
};
}),
);
expect(result.removed).toBe(true);
expect(result.removedAgain, "a repeated removal changes nothing").toBe(false);
expect(result.tombstone?.status).toBe("inactive");
expect(result.stale, "the pre-removal payload is refused").toBe(false);
expect(result.replaced, "a replacement newer than the row's stamp is accepted").toBe(true);
expect(result.afterReplace).toMatchObject({
status: "active",
membershipId: `${result.membershipId}_2`,
});
expect(result.minted, "a row the mirror never held is still tombstoned").toBe(true);
expect(result.mintedRow?.status).toBe("inactive");
});
it("tombstones a membership the mirror has not seen, so a later older payload cannot insert it live", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const membershipId = `om_${id}_${org}`;
const readdedId = `${membershipId}_2`;
const ref = { id: membershipId, accountId: id, organizationId: org };
// The reconciler applies the deletion before the backfill has
// inserted the row (the user is unknown too).
const removed = yield* mirror.deleteMembership(ref, T2);
const removedAgain = yield* mirror.deleteMembership(ref, T2);
const tombstone = yield* directory.membership(id, org, ["inactive"]);
// The backfill, listing WorkOS as it was before the deletion, now
// writes the membership: refused, the tombstone stands.
const backfilled = yield* mirror.upsertMembership(
membership(org, id, { id: membershipId, updatedAt: T1 }),
);
const afterBackfill = yield* directory.membership(id, org);
// The user payload still fills the bare account row the tombstone
// minted, so the inactive row reads with its profile.
yield* mirror.upsertUser(user(id, { firstName: "Late", updatedAt: T1 }));
const profiled = yield* directory.membership(id, org, ["inactive"]);
// Re-added in WorkOS later under a NEW membership id.
const readded = yield* mirror.upsertMembership(
membership(org, id, { id: readdedId, updatedAt: T3 }),
);
const afterReadd = yield* directory.membership(id, org);
return {
readdedId,
removed,
removedAgain,
tombstone,
backfilled,
afterBackfill,
profiled,
readded,
afterReadd,
};
}),
);
expect(result.removed, "the delete leaves a tombstone behind").toBe(true);
expect(result.removedAgain).toBe(false);
expect(result.tombstone).toMatchObject({ status: "inactive", email: null });
expect(result.backfilled, "the pre-deletion payload is refused").toBe(false);
expect(result.afterBackfill, "and the member is not live").toBeNull();
expect(result.profiled?.name).toBe("Late");
expect(result.readded).toBe(true);
expect(result.afterReadd).toMatchObject({
status: "active",
membershipId: result.readdedId,
});
});
it("records a delete whose id the row does not hold, so a newer payload of that id cannot take the row over", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const membershipA = `om_${id}_${org}_a`;
const membershipB = `om_${id}_${org}_b`;
yield* mirror.upsertUser(user(id));
// The mirror holds A (a stale listing). In WorkOS, A was already
// replaced by B (stamped T2), and B is then deleted (T3) before any
// feeder wrote B here.
yield* mirror.upsertMembership(membership(org, id, { id: membershipA, updatedAt: T1 }));
const refB = { id: membershipB, accountId: id, organizationId: org };
const deletedB = yield* mirror.deleteMembership(refB, T3);
const deletedBAgain = yield* mirror.deleteMembership(refB, T3);
const rowAfterDelete = yield* directory.membership(id, org);
// A delayed scan, listed before B's deletion, now writes B: stamped
// after A and under another id, exactly what the row guard lets
// through — the ledger refuses it.
const lateB = yield* mirror.upsertMembership(
membership(org, id, { id: membershipB, updatedAt: T2 }),
);
const afterLateB = yield* directory.membership(id, org);
// A's own deletion, applied later, tombstones the row it holds.
const deletedA = yield* mirror.deleteMembership(
{ id: membershipA, accountId: id, organizationId: org },
T3,
);
const afterDeleteA = yield* directory.membership(id, org);
// The member re-added in WorkOS under a third id: accepted.
const readded = yield* mirror.upsertMembership(
membership(org, id, { id: `${membershipB}_c`, updatedAt: T4 }),
);
const afterReadd = yield* directory.membership(id, org);
return {
deletedB,
deletedBAgain,
rowAfterDelete,
lateB,
afterLateB,
deletedA,
afterDeleteA,
readded,
afterReadd,
membershipA,
};
}),
);
expect(result.deletedB, "the delete is recorded even though the row holds another id").toBe(
true,
);
expect(result.deletedBAgain, "a replayed delete records nothing new").toBe(false);
expect(
result.rowAfterDelete?.membershipId,
"the row under A is not B's to tombstone and stands",
).toBe(result.membershipA);
expect(result.lateB, "the newer payload of the deleted id is refused: identity, not time").toBe(
false,
);
expect(result.afterLateB?.membershipId).toBe(result.membershipA);
expect(result.deletedA).toBe(true);
expect(result.afterDeleteA, "A's deletion tombstones the row").toBeNull();
expect(result.readded, "a replacement under a fresh id reactivates").toBe(true);
expect(result.afterReadd?.status).toBe("active");
});
it("reactivates a membership WorkOS deactivated under the same id: a deactivation is not a deletion", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const membershipId = `om_${id}_${org}`;
yield* mirror.upsertUser(user(id));
yield* mirror.upsertMembership(membership(org, id, { id: membershipId, updatedAt: T1 }));
// WorkOS deactivates the membership (an `organization_membership.updated`
// with status inactive): the row is inactive but still WorkOS's, with
// no `deleted_at` to protect it.
const deactivated = yield* mirror.upsertMembership(
membership(org, id, {
id: membershipId,
status: "inactive",
updatedAt: T2,
}),
);
const whileInactive = yield* directory.membership(id, org);
// A payload older than the deactivation cannot undo it, nor one
// stamped at the same instant.
const older = yield* mirror.upsertMembership(
membership(org, id, { id: membershipId, updatedAt: T1 }),
);
const equal = yield* mirror.upsertMembership(
membership(org, id, { id: membershipId, updatedAt: T2 }),
);
// WorkOS reactivates it, same id, newer stamp: live again.
const reactivated = yield* mirror.upsertMembership(
membership(org, id, {
id: membershipId,
role: "admin",
updatedAt: T3,
}),
);
const afterReactivate = yield* directory.membership(id, org);
return {
membershipId,
deactivated,
whileInactive,
older,
equal,
reactivated,
afterReactivate,
};
}),
);
expect(result.deactivated).toBe(true);
expect(result.whileInactive, "an inactive membership reads as no membership").toBeNull();
expect(result.older, "an older payload cannot undo the deactivation").toBe(false);
expect(result.equal, "nor one stamped at the deactivation").toBe(false);
expect(result.reactivated, "a newer payload under the same id reactivates it").toBe(true);
expect(result.afterReactivate).toMatchObject({
status: "active",
role: "admin",
membershipId: result.membershipId,
});
});
it("tombstones every membership of a deleted user and clears the profile, keeping the account row", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const orgA = yield* freshOrg();
const orgB = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
yield* mirror.upsertUser(user(id, { firstName: "Gone", updatedAt: T1 }));
yield* mirror.upsertMembership(membership(orgA, id));
yield* mirror.upsertMembership(membership(orgB, id));
const deleted = yield* mirror.deleteUser(id, T2);
const deletedAgain = yield* mirror.deleteUser(id, T2);
const inA = yield* directory.membership(id, orgA);
const inB = yield* directory.membership(id, orgB, ["inactive"]);
// A stale user payload cannot restore the profile, nor can one
// stamped at the deletion itself. No membership of the deleted user
// is written again — not the deleted id (a deleted membership id
// never returns), and not one stamped after the deletion under a
// NEW id, the payload a timestamp guard would let through: the user
// is gone, and WorkOS never reuses the id.
const staleUser = yield* mirror.upsertUser(user(id, { firstName: "Back", updatedAt: T1 }));
const equalUser = yield* mirror.upsertUser(user(id, { firstName: "Same", updatedAt: T2 }));
const sameId = yield* mirror.upsertMembership(membership(orgA, id, { updatedAt: T3 }));
const rejoined = yield* mirror.upsertMembership(
membership(orgA, id, { id: `om_${id}_${orgA}_2`, updatedAt: T3 }),
);
const afterRejoin = yield* directory.membership(id, orgA, ["inactive"]);
// A user the mirror has never seen: the delete mints the account
// tombstone, the backfill's older profile cannot fill it afterwards,
// and a membership of the user the mirror has never seen — no row
// for the membership guard to judge — is refused by the account
// tombstone alone, whatever it is stamped.
const unseen = `user_${crypto.randomUUID()}`;
const unknown = yield* mirror.deleteUser(unseen, T2);
const unseenProfile = yield* mirror.upsertUser(
user(unseen, { firstName: "Ghost", updatedAt: T1 }),
);
const unseenMembership = yield* mirror.upsertMembership(
membership(orgA, unseen, { updatedAt: T3 }),
);
const unseenRow = yield* directory.membership(unseen, orgA, [
"active",
"pending",
"inactive",
]);
return {
deleted,
deletedAgain,
inA,
inB,
staleUser,
equalUser,
sameId,
rejoined,
afterRejoin,
unknown,
unseenProfile,
unseenMembership,
unseenRow,
};
}),
);
expect(result.deleted).toBe(true);
expect(result.deletedAgain, "a replayed delete changes nothing").toBe(false);
expect(result.inA, "the user's memberships are tombstoned").toBeNull();
expect(result.inB).toMatchObject({
status: "inactive",
email: null,
name: null,
});
expect(result.staleUser).toBe(false);
expect(result.equalUser, "a profile stamped AT the deletion is refused").toBe(false);
expect(result.sameId, "a deleted membership id never returns").toBe(false);
expect(
result.rejoined,
"a membership of a deleted user is refused however it is stamped: identity, not time",
).toBe(false);
expect(result.afterRejoin).toMatchObject({
status: "inactive",
name: null,
});
expect(result.unknown, "deleting an unseen user leaves a tombstone").toBe(true);
expect(result.unseenProfile, "which the older profile cannot fill").toBe(false);
expect(
result.unseenMembership,
"and a membership the mirror never held is not inserted for the deleted user",
).toBe(false);
expect(result.unseenRow).toBeNull();
});
it("waits for a user deletion holding the account row before judging a membership write against its tombstone", async () => {
// A feeder (a stalled login list, the backfill's older listing) writes a
// membership of a user whose `user.deleted` the reconciler is applying
// at this moment. The deletion locks the account row for the length of
// its transaction; the feeder's write must wait for that commit, see the
// tombstone, and refuse the payload — never insert a live membership
// for a deleted user. The deletion runs on the test's shared connection;
// the feeder runs the same store over a second one, so the two
// transactions are real peers on the server.
const result = await run(
Effect.scoped(
Effect.gen(function* () {
const org = yield* freshOrg();
const gone = `user_${crypto.randomUUID()}`;
const mirror = yield* WorkOsMirror;
const { db: deleterDb } = yield* DbService;
// The second connection is owned by this test body's scope.
const feederDb = Context.get(yield* Layer.build(makeDbLayer()), DbService).db;
const feeder = makeWorkOsMirrorStore(feederDb);
const directory = yield* MemberDirectory;
yield* mirror.upsertUser(user(gone, { firstName: "Gone" }));
// The deletion's transaction: the account row is locked as
// `deleteUser` locks it, and the transaction is held open until
// `release` — the window a feeder can race. The tombstone itself
// is written after the feeder has started waiting.
const locked = yield* Deferred.make();
let release: () => void = () => undefined;
const held = new Promise((resolve) => {
release = resolve;
});
const deletion = yield* Effect.forkChild(
Effect.promise(() =>
deleterDb.transaction(async (tx) => {
await tx
.select({ id: accounts.id })
.from(accounts)
.where(eq(accounts.id, gone))
.for("no key update");
await Effect.runPromise(Deferred.succeed(locked, undefined));
await held;
await makeWorkOsMirrorStore(tx).deleteUser(gone, T2).pipe(Effect.runPromise);
}),
),
{ startImmediately: true },
);
yield* Deferred.await(locked);
// The feeder's write starts while the deletion holds the row.
const written = yield* Deferred.make();
yield* Effect.forkChild(
feeder
.upsertMembership(membership(org, gone, { updatedAt: T3 }))
.pipe(Effect.flatMap((wrote) => Deferred.succeed(written, wrote))),
{ startImmediately: true },
);
const beforeCommit = yield* Effect.timeoutOption(Deferred.await(written), "250 millis");
release();
yield* Fiber.join(deletion);
const afterCommit = yield* Deferred.await(written);
const row = yield* directory.membership(gone, org, ["active", "pending", "inactive"]);
return { beforeCommit, afterCommit, row };
}),
),
);
expect(
Option.isNone(result.beforeCommit),
"the write waits while the deletion holds the account row",
).toBe(true);
expect(
result.afterCommit,
"once the deletion has committed, its tombstone refuses the payload",
).toBe(false);
expect(result.row, "so no membership was inserted for the deleted user").toBeNull();
});
it("waits for a membership deletion holding the account row before judging a write of that id against the ledger", async () => {
// Same race for a single membership: a feeder writes membership `om`
// while the reconciler is applying its `organization_membership.deleted`.
// The delete locks the account row for its transaction, so the feeder's
// FOR SHARE read waits, then finds the ledger entry and refuses. Without
// the lock the feeder could pass the ledger check first and insert the
// deleted membership live after the delete committed.
const result = await run(
Effect.scoped(
Effect.gen(function* () {
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
const mirror = yield* WorkOsMirror;
const { db: deleterDb } = yield* DbService;
const feederDb = Context.get(yield* Layer.build(makeDbLayer()), DbService).db;
const feeder = makeWorkOsMirrorStore(feederDb);
const directory = yield* MemberDirectory;
yield* mirror.upsertUser(user(id));
const gone = membership(org, id, { updatedAt: T3 });
const locked = yield* Deferred.make();
let release: () => void = () => undefined;
const held = new Promise((resolve) => {
release = resolve;
});
const deletion = yield* Effect.forkChild(
Effect.promise(() =>
deleterDb.transaction(async (tx) => {
await tx
.select({ id: accounts.id })
.from(accounts)
.where(eq(accounts.id, id))
.for("no key update");
await Effect.runPromise(Deferred.succeed(locked, undefined));
await held;
await makeWorkOsMirrorStore(tx).deleteMembership(gone, T2).pipe(Effect.runPromise);
}),
),
{ startImmediately: true },
);
yield* Deferred.await(locked);
const written = yield* Deferred.make();
yield* Effect.forkChild(
feeder
.upsertMembership(gone)
.pipe(Effect.flatMap((wrote) => Deferred.succeed(written, wrote))),
{ startImmediately: true },
);
const beforeCommit = yield* Effect.timeoutOption(Deferred.await(written), "250 millis");
release();
yield* Fiber.join(deletion);
const afterCommit = yield* Deferred.await(written);
const row = yield* directory.membership(id, org, ["active", "pending", "inactive"]);
return { beforeCommit, afterCommit, status: row?.status ?? null };
}),
),
);
expect(
Option.isNone(result.beforeCommit),
"the write waits while the deletion holds the account row",
).toBe(true);
expect(result.afterCommit, "once the deletion has committed, the ledger refuses the id").toBe(
false,
);
expect(result.status, "and the row is the tombstone, never live").toBe("inactive");
});
});
describe("WorkOsMirror cursor", () => {
it("advances only by compare-and-set, and a page that loses the CAS writes nothing", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
// The cursor is instance-wide; read whatever a previous test left so
// this test's expectations are relative, not absolute.
const before = yield* mirror.getCursor();
const first = yield* mirror.applyPage(before, "event_1", []);
const wrongPrev = yield* mirror.applyPage(before === null ? "event_0" : null, "event_x", [
WorkOsMirrorWrite.UpsertUser({ user: user(id) }),
WorkOsMirrorWrite.UpsertMembership({
membership: membership(org, id),
}),
]);
const afterWrong = yield* mirror.getCursor();
const notWritten = yield* directory.membership(id, org);
const right = yield* mirror.applyPage("event_1", "event_2", [
WorkOsMirrorWrite.UpsertUser({ user: user(id) }),
WorkOsMirrorWrite.UpsertMembership({
membership: membership(org, id),
}),
// A rename of an org the mirror has never seen: nothing to write.
WorkOsMirrorWrite.RenameOrganization({
organizationId: "org_nobody",
name: "Nobody",
updatedAt: T1,
}),
]);
const after = yield* mirror.getCursor();
const written = yield* directory.membership(id, org);
return {
id,
org,
first,
wrongPrev,
afterWrong,
notWritten,
right,
after,
written,
};
}),
);
expect(Option.isSome(result.first)).toBe(true);
expect(
Option.isNone(result.wrongPrev),
"a run holding a stale prev cannot move the cursor",
).toBe(true);
expect(result.afterWrong).toBe("event_1");
expect(result.notWritten, "and none of its page's writes land").toBeNull();
expect(result.right).toEqual(Option.some(["applied", "applied", "absent"]));
expect(result.after).toBe("event_2");
expect(result.written?.membershipId).toBe(`om_${result.id}_${result.org}`);
});
});
describe("mirror readiness", () => {
const now = T4;
const budget = Duration.toMillis(MIRROR_RECONCILER_LAG_BUDGET);
const within = new Date(now.getTime() - budget);
const tooOld = new Date(now.getTime() - budget - 1);
it("is ready only when the backfill has completed AND the reconciler drained within the budget", () => {
expect(mirrorReadinessFrom(null, now), "no events row: never backfilled").toEqual(
MirrorReadinessState.BackfillPending(),
);
expect(mirrorReadinessFrom({ backfillCompletedAt: null, drainedAt: within }, now)).toEqual(
MirrorReadinessState.BackfillPending(),
);
expect(
mirrorReadinessFrom({ backfillCompletedAt: T1, drainedAt: null }, now),
"backfilled but the reconciler has never drained",
).toEqual(MirrorReadinessState.ReconcilerStale({ drainedAt: null }));
expect(
mirrorReadinessFrom({ backfillCompletedAt: T1, drainedAt: tooOld }, now),
"a drain older than the budget is stale",
).toEqual(MirrorReadinessState.ReconcilerStale({ drainedAt: tooOld }));
expect(
mirrorReadinessFrom({ backfillCompletedAt: T1, drainedAt: within }, now),
"a drain exactly at the budget is still ready",
).toEqual(MirrorReadinessState.Ready());
expect(mirrorReadinessFrom({ backfillCompletedAt: T1, drainedAt: now }, now)).toEqual(
MirrorReadinessState.Ready(),
);
});
it("reads the live row the backfill and the reconciler write", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const { db } = yield* DbService;
const readiness = () => Effect.promise(() => readMirrorReadiness(db, new Date()));
yield* clearEventsRow;
const noRow = yield* readiness();
yield* mirror.setReplayBoundary(T1);
yield* mirror.markBackfillCompleted(T1);
const backfilledOnly = yield* readiness();
// A drain as of now: what a reconciler run that just read the stream
// to its end records.
const drainedAt = new Date();
yield* mirror.markDrained(drainedAt);
const ready = yield* readiness();
return { noRow, backfilledOnly, ready };
}),
);
expect(result.noRow).toEqual(MirrorReadinessState.BackfillPending());
expect(result.backfilledOnly).toEqual(
MirrorReadinessState.ReconcilerStale({ drainedAt: null }),
);
expect(result.ready).toEqual(MirrorReadinessState.Ready());
});
});
describe("WorkOsMirror backfill sync state", () => {
it("records the replay boundary and the backfill completion once each, and the drained mark forward only, without touching the cursor", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
// The events row is instance-wide (migration 0019 seeds it on the
// empty test database, other tests may have written it): start from
// no row, as a database that has never been backfilled has.
yield* clearEventsRow;
// No events row yet: nothing has been drained, and nothing is minted.
const drainedWithoutRow = yield* mirror.markDrained(T1);
const first = yield* mirror.setReplayBoundary(T2);
const boundary = yield* mirror.replayBoundary();
const cursorAfterBoundary = yield* mirror.getCursor();
// A later completed backfill: its boundary is not recorded.
const again = yield* mirror.setReplayBoundary(T3);
const afterAgain = yield* mirror.replayBoundary();
// Nor once the stream is being followed.
const cursorBefore = yield* mirror.getCursor();
yield* mirror.applyPage(cursorBefore, "event_boundary", []);
const afterCursor = yield* mirror.setReplayBoundary(T1);
const boundaryWithCursor = yield* mirror.replayBoundary();
const cursor = yield* mirror.getCursor();
// The completion mark: absent until a run covers every organization,
// then written once, beside the boundary and the cursor.
const notCompleted = yield* mirror.backfillCompletedAt();
const completed = yield* mirror.markBackfillCompleted(T3);
const completedAgain = yield* mirror.markBackfillCompleted(T4);
const completedAt = yield* mirror.backfillCompletedAt();
const boundaryAfterCompletion = yield* mirror.replayBoundary();
const cursorAfterCompletion = yield* mirror.getCursor();
// The drained mark moves forward only, on the row the stream owns.
const notDrained = yield* mirror.drainedAt();
const drainedFirst = yield* mirror.markDrained(T3);
const drainedBackwards = yield* mirror.markDrained(T2);
const drainedForward = yield* mirror.markDrained(T4);
const drainedAt = yield* mirror.drainedAt();
return {
drainedWithoutRow,
notDrained,
drainedFirst,
drainedBackwards,
drainedForward,
drainedAt,
first,
boundary,
cursorAfterBoundary,
again,
afterAgain,
afterCursor,
boundaryWithCursor,
cursor,
notCompleted,
completed,
completedAgain,
completedAt,
boundaryAfterCompletion,
cursorAfterCompletion,
};
}),
);
expect(result.first, "the first boundary is recorded").toBe(true);
expect(result.boundary, "and reads back as written").toEqual(T2);
expect(result.cursorAfterBoundary, "writing the boundary mints no cursor").toBeNull();
expect(result.again, "a later run's boundary is refused").toBe(false);
expect(result.afterAgain, "the first stands").toEqual(T2);
expect(result.afterCursor).toBe(false);
expect(result.boundaryWithCursor).toEqual(T2);
expect(result.cursor, "and the cursor is untouched").toBe("event_boundary");
expect(result.notCompleted, "no completion until a run covers every org").toBeNull();
expect(result.completed, "the first completion is recorded").toBe(true);
expect(result.completedAgain, "a later one is refused").toBe(false);
expect(result.completedAt, "the first stands").toEqual(T3);
expect(result.boundaryAfterCompletion, "the boundary is untouched").toEqual(T2);
expect(result.cursorAfterCompletion, "and so is the cursor").toBe("event_boundary");
expect(result.drainedWithoutRow, "no row, nothing drained: nothing written").toBe(false);
expect(result.notDrained, "no drain recorded until a run drains").toBeNull();
expect(result.drainedFirst).toBe(true);
expect(result.drainedBackwards, "an earlier run finishing later cannot move it back").toBe(
false,
);
expect(result.drainedForward).toBe(true);
expect(result.drainedAt).toEqual(T4);
});
it("refuses a membership payload stamped before the organization's last scan, and accepts one stamped at or after it", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const revoked = `user_${crypto.randomUUID()}`;
const kept = `user_${crypto.randomUUID()}`;
const joined = `user_${crypto.randomUUID()}`;
// A login fetched its membership list at T1, while `revoked` was a
// member, then stalled. WorkOS revoked them, and the backfill scanned
// the org at T2 without them — nothing to tombstone, the row was
// never there.
yield* mirror.applyOrganizationScan({
organizationId: org,
listedAt: T2,
members: [
{
user: user(kept),
membership: membership(org, kept, { updatedAt: T1 }),
},
],
});
// The stalled login resumes and writes what it holds: refused, the
// revocation predates the scan and nothing would ever undo the row.
const stale = yield* mirror.upsertMembership(membership(org, revoked, { updatedAt: T1 }));
const staleRow = yield* directory.membership(revoked, org, [
"active",
"pending",
"inactive",
]);
// The same list's payload for a member the scan kept: refused too —
// it changes nothing, the scan already wrote that state.
const repeated = yield* mirror.upsertMembership(membership(org, kept, { updatedAt: T1 }));
const keptRow = yield* directory.membership(kept, org);
// A membership WorkOS created after the scan (its event, or a login
// after it): stamped past the mark, written.
const later = yield* mirror.upsertMembership(membership(org, joined, { updatedAt: T3 }));
const atMark = yield* mirror.upsertMembership(
membership(org, kept, { role: "admin", updatedAt: T2 }),
);
const keptAfter = yield* directory.membership(kept, org);
return { stale, staleRow, repeated, keptRow, later, atMark, keptAfter };
}),
);
expect(result.stale, "a payload older than the scan is refused").toBe(false);
expect(result.staleRow, "and no row is minted for the revoked member").toBeNull();
expect(result.repeated, "even for a member the scan kept").toBe(false);
expect(result.keptRow?.status).toBe("active");
expect(result.later, "a payload newer than the scan is written").toBe(true);
expect(result.atMark, "as is one stamped at the scan's instant").toBe(true);
expect(result.keptAfter?.role).toBe("admin");
});
it("waits for a scan holding the organization row before judging a payload against the scan's mark", async () => {
// A feeder (a login) writes a membership it fetched at T1 while a scan
// listed at T2 — which no longer contains that membership — is being
// applied. The scan claims the organization row for the length of its
// transaction; the feeder's write must wait for that commit, observe
// the mark, and refuse the payload — never insert the membership the
// scan proved revoked. The scan holds the test's shared connection; the
// feeder runs the same store over a second one, so the two transactions
// are real peers on the server.
const result = await run(
Effect.scoped(
Effect.gen(function* () {
const org = yield* freshOrg();
const revoked = `user_${crypto.randomUUID()}`;
const { db: scanDb } = yield* DbService;
// The second connection is owned by this test body's scope.
const feederDb = Context.get(yield* Layer.build(makeDbLayer()), DbService).db;
const feeder = makeWorkOsMirrorStore(feederDb);
const directory = yield* MemberDirectory;
// The scan's transaction: its claim (the `backfilled_at` CAS, an
// UPDATE that locks the row) is done, and the transaction is held
// open until `release` — the window a feeder can race.
const claimed = yield* Deferred.make();
let release: () => void = () => undefined;
const held = new Promise((resolve) => {
release = resolve;
});
const scan = yield* Effect.forkChild(
Effect.promise(() =>
scanDb.transaction(async (tx) => {
await tx
.update(organizations)
.set({ backfilledAt: T2 })
.where(eq(organizations.id, org));
await Effect.runPromise(Deferred.succeed(claimed, undefined));
await held;
}),
),
{ startImmediately: true },
);
yield* Deferred.await(claimed);
// The feeder's write starts while the scan holds the row.
const written = yield* Deferred.make();
yield* Effect.forkChild(
feeder
.upsertMembership(membership(org, revoked, { updatedAt: T1 }))
.pipe(Effect.flatMap((wrote) => Deferred.succeed(written, wrote))),
{ startImmediately: true },
);
const beforeCommit = yield* Effect.timeoutOption(Deferred.await(written), "250 millis");
release();
yield* Fiber.join(scan);
const afterCommit = yield* Deferred.await(written);
const row = yield* directory.membership(revoked, org, ["active", "pending", "inactive"]);
return { beforeCommit, afterCommit, row };
}),
),
);
expect(
Option.isNone(result.beforeCommit),
"the write waits while the scan holds the organization row",
).toBe(true);
expect(result.afterCommit, "once the scan has committed, its mark refuses the payload").toBe(
false,
);
expect(result.row, "so the revoked membership was never inserted").toBeNull();
});
it("applies a scan only when its listing is newer than the one already applied, and never to a deleted or unknown organization", async () => {
const result = await run(
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const directory = yield* MemberDirectory;
const store = yield* UserStoreService;
const scanned = yield* freshOrg();
const untouched = yield* freshOrg();
const staying = `user_${crypto.randomUUID()}`;
const leaving = `user_${crypto.randomUUID()}`;
const member = (id: string) => ({
user: user(id),
membership: membership(scanned, id),
});
const before = yield* mirror.organizationBackfilledAt(scanned);
// The later listing (T2) no longer contains `leaving`; applied first.
const later = yield* mirror.applyOrganizationScan({
organizationId: scanned,
listedAt: T2,
members: [member(staying)],
});
const afterLater = yield* mirror.organizationBackfilledAt(scanned);
// The earlier listing (T1) still contains `leaving`: refused whole.
const earlier = yield* mirror.applyOrganizationScan({
organizationId: scanned,
listedAt: T1,
members: [member(staying), member(leaving)],
});
const afterEarlier = yield* mirror.organizationBackfilledAt(scanned);
const leavingRow = yield* directory.membership(leaving, scanned, [
"active",
"pending",
"inactive",
]);
// The same instant is not newer either: a replayed listing writes nothing.
const same = yield* mirror.applyOrganizationScan({
organizationId: scanned,
listedAt: T2,
members: [],
});
const stayingRow = yield* directory.membership(staying, scanned);
const other = yield* mirror.organizationBackfilledAt(untouched);
const unknown = yield* mirror.applyOrganizationScan({
organizationId: "org_never_mirrored",
listedAt: T3,
members: [],
});
yield* store.use("deleteOrganizationCascade", (s) =>
s.deleteOrganizationCascade(untouched, T3),
);
const deleted = yield* mirror.applyOrganizationScan({
organizationId: untouched,
listedAt: T4,
members: [{ user: user(staying), membership: membership(untouched, staying) }],
});
const deletedRow = yield* directory.membership(staying, untouched);
return {
before,
later,
afterLater,
earlier,
afterEarlier,
leavingRow,
same,
stayingRow,
other,
unknown,
deleted,
deletedRow,
};
}),
);
expect(result.before, "a freshly mirrored organization is unscanned").toBeNull();
expect(result.later).toEqual(
Option.some({
usersWritten: 1,
membershipsWritten: 1,
membershipsTombstoned: 0,
}),
);
expect(result.afterLater, "the scan marks the organization as of its listing").toEqual(T2);
expect(result.earlier, "an older listing is refused whole").toEqual(Option.none());
expect(result.afterEarlier, "and the mark never moves backwards").toEqual(T2);
expect(
result.leavingRow,
"the membership only the older listing held was never written",
).toBeNull();
expect(result.same, "a listing at the recorded instant is refused too").toEqual(Option.none());
expect(result.stayingRow?.status, "so it tombstones nothing the newer one wrote").toBe(
"active",
);
expect(result.other, "another organization's mark is its own").toBeNull();
expect(result.unknown, "an organization the mirror does not hold takes no scan").toEqual(
Option.none(),
);
expect(result.deleted, "nor does a deleted one, however new the listing").toEqual(
Option.none(),
);
expect(result.deletedRow).toBeNull();
});
});
describe("cloud MemberDirectory", () => {
const seed = (org: string) =>
Effect.gen(function* () {
const mirror = yield* WorkOsMirror;
const ids = {
ada: `user_${crypto.randomUUID()}`,
grace: `user_${crypto.randomUUID()}`,
linus: `user_${crypto.randomUUID()}`,
gone: `user_${crypto.randomUUID()}`,
};
yield* mirror.upsertUser(
user(ids.ada, {
email: "Ada.Lovelace@Placeholder.test",
firstName: "Ada",
lastName: "Lovelace",
lastSignInAt: T2,
}),
);
yield* mirror.upsertUser(
user(ids.grace, {
email: "grace@placeholder.test",
firstName: "Grace",
lastName: "Hopper",
}),
);
yield* mirror.upsertUser(
user(ids.linus, {
email: "linus@placeholder.test",
firstName: "Linus",
lastName: null,
}),
);
yield* mirror.upsertUser(user(ids.gone, { email: "gone@placeholder.test" }));
yield* mirror.upsertMembership(membership(org, ids.ada, { role: "admin" }));
yield* mirror.upsertMembership(membership(org, ids.grace, { status: "pending" }));
yield* mirror.upsertMembership(membership(org, ids.linus));
yield* mirror.upsertMembership(membership(org, ids.gone, { status: "inactive" }));
return ids;
});
it("lists active + pending members by default, ordered by email, and pages stably", async () => {
const result = await run(
Effect.gen(function* () {
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const ids = yield* seed(org);
const all = yield* directory.members(org);
const page1 = yield* directory.members(org, { limit: 2, offset: 0 });
const page2 = yield* directory.members(org, { limit: 2, offset: 2 });
const inactive = yield* directory.members(org, {
statuses: ["inactive"],
});
return { ids, all, page1, page2, inactive };
}),
);
expect(result.all.map((m) => m.email)).toEqual([
"Ada.Lovelace@Placeholder.test",
"grace@placeholder.test",
"linus@placeholder.test",
]);
expect(result.all.find((m) => m.accountId === result.ids.ada)).toMatchObject({
role: "admin",
status: "active",
name: "Ada Lovelace",
lastActiveAt: T2.getTime(),
});
expect(result.all.find((m) => m.accountId === result.ids.linus)?.name).toBe("Linus");
expect([...result.page1, ...result.page2].map((m) => m.accountId)).toEqual(
result.all.map((m) => m.accountId),
);
expect(result.inactive.map((m) => m.accountId)).toEqual([result.ids.gone]);
});
it("searches email and name case-insensitively, escaping LIKE wildcards", async () => {
const result = await run(
Effect.gen(function* () {
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const ids = yield* seed(org);
const byEmail = yield* directory.members(org, { search: "LOVELACE@" });
const byName = yield* directory.members(org, {
search: " grace hop ",
});
const nothing = yield* directory.members(org, { search: "nobody" });
const blank = yield* directory.members(org, { search: " " });
const wildcard = yield* directory.members(org, { search: "%" });
return { ids, byEmail, byName, nothing, blank, wildcard };
}),
);
expect(result.byEmail.map((m) => m.accountId)).toEqual([result.ids.ada]);
expect(result.byName.map((m) => m.accountId)).toEqual([result.ids.grace]);
expect(result.nothing).toEqual([]);
expect(result.blank.length, "a blank term is no filter").toBe(3);
expect(result.wildcard, "a literal % matches nothing rather than everything").toEqual([]);
});
it("lists one account's memberships across orgs, active + pending by default", async () => {
const result = await run(
Effect.gen(function* () {
const directory = yield* MemberDirectory;
const mirror = yield* WorkOsMirror;
const active = yield* freshOrg();
const pending = yield* freshOrg();
const inactive = yield* freshOrg();
const id = `user_${crypto.randomUUID()}`;
yield* mirror.upsertUser(user(id));
yield* mirror.upsertMembership(membership(active, id, { role: "admin" }));
yield* mirror.upsertMembership(membership(pending, id, { status: "pending" }));
yield* mirror.upsertMembership(membership(inactive, id, { status: "inactive" }));
const defaults = yield* directory.membershipsOf(id);
const activeOnly = yield* directory.membershipsOf(id, ["active"]);
const nobody = yield* directory.membershipsOf(`user_${crypto.randomUUID()}`);
return { active, pending, inactive, defaults, activeOnly, nobody };
}),
);
expect(result.defaults.map((m) => m.organizationId)).toEqual(
[result.active, result.pending].sort(),
);
expect(result.defaults.find((m) => m.organizationId === result.active)?.role).toBe("admin");
expect(result.activeOnly.map((m) => m.organizationId)).toEqual([result.active]);
expect(result.nobody).toEqual([]);
});
it("resolves a normalized email regardless of stored casing, and batches by id", async () => {
const result = await run(
Effect.gen(function* () {
const directory = yield* MemberDirectory;
const org = yield* freshOrg();
const other = yield* freshOrg();
const ids = yield* seed(org);
const found = yield* directory.findByEmail(org, "ada.lovelace@placeholder.test");
const inactive = yield* directory.findByEmail(org, "gone@placeholder.test");
const inactiveAsked = yield* directory.findByEmail(org, "gone@placeholder.test", [
"inactive",
]);
const wrongOrg = yield* directory.findByEmail(other, "ada.lovelace@placeholder.test");
const batch = yield* directory.membersById(org, [ids.ada, ids.gone, "user_unknown"]);
const batchAll = yield* directory.membersById(
org,
[ids.ada, ids.gone],
["active", "pending", "inactive"],
);
const empty = yield* directory.membersById(org, []);
const byId = yield* directory.membershipById(org, `om_${ids.gone}_${org}`);
const byIdForeign = yield* directory.membershipById(other, `om_${ids.gone}_${org}`);
const byIdUnknown = yield* directory.membershipById(org, "om_unknown");
return {
ids,
found,
inactive,
inactiveAsked,
wrongOrg,
batch,
batchAll,
empty,
byId,
byIdForeign,
byIdUnknown,
};
}),
);
expect(result.found?.accountId).toBe(result.ids.ada);
expect(result.inactive, "an inactive member is not found by default").toBeNull();
expect(result.inactiveAsked?.status, "but is when asked for").toBe("inactive");
expect(result.wrongOrg).toBeNull();
expect([...result.batch.keys()], "a batch excludes inactive by default").toEqual([
result.ids.ada,
]);
expect([...result.batchAll.keys()].sort()).toEqual([result.ids.ada, result.ids.gone].sort());
expect(result.empty.size).toBe(0);
expect(result.byId, "membershipById reports any status").toMatchObject({
accountId: result.ids.gone,
status: "inactive",
});
expect(result.byIdForeign, "an id from another org is not this org's").toBeNull();
expect(result.byIdUnknown).toBeNull();
});
});