import { describe, it, expect } from "@effect/vitest";import { Data, Effect, Layer } from "effect"; import { AuthContext } from "../auth/middleware";import { WorkOSAuth, type WorkOSAuthService } from "../auth/workos";import { Forbidden } from "./api"; // ---------------------------------------------------------------------------// Stub factory — only implement what each test calls// --------------------------------------------------------------------------- // eslint-disable-next-line @typescript-eslint/no-explicit-any -- test stub needs wide function typestype StubFn = (...args: never[]) => Effect.Effect<any>; type StubOverrides = { listOrgMembers?: StubFn; getUser?: StubFn; sendInvitation?: StubFn; deleteOrgMembership?: StubFn; updateOrgMembershipRole?: StubFn; listOrgRoles?: StubFn;}; class UnstubbedWorkOSMethod extends Data.TaggedError("UnstubbedWorkOSMethod")<{ method: string;}> {} const stubWorkOS = (overrides: StubOverrides = {}) => Layer.succeed( WorkOSAuth, new Proxy({} as WorkOSAuthService, { get: (_target, prop) => { if (typeof prop === "string" && prop in overrides) { return overrides[prop as keyof StubOverrides]; } return () => Effect.fail( new UnstubbedWorkOSMethod({ method: typeof prop === "string" ? prop : (prop.description ?? "symbol"), }), ); }, }), ); // ---------------------------------------------------------------------------// Fixtures// --------------------------------------------------------------------------- const adminAuth = { accountId: "user_admin", organizationId: "org_1", email: "admin@test.com", name: "Admin", avatarUrl: null,}; const memberAuth = { accountId: "user_member", organizationId: "org_1", email: "member@test.com", name: "Member", avatarUrl: null,}; type FakeMembership = { id: string; userId: string; status: string; role: { slug: string };};type FakeUser = { email: string; firstName: string | null; lastName: string | null; profilePictureUrl: string | null; lastSignInAt: string | null;};type FakeRole = { slug: string; name: string }; const fakeMemberships: FakeMembership[] = [ { id: "mem_admin", userId: "user_admin", status: "active", role: { slug: "admin" }, }, { id: "mem_member", userId: "user_member", status: "active", role: { slug: "member" }, },]; const fakeUsers: Record<string, FakeUser> = { user_admin: { email: "admin@test.com", firstName: "Admin", lastName: null, profilePictureUrl: null, lastSignInAt: "2026-04-09T00:00:00Z", }, user_member: { email: "member@test.com", firstName: "Member", lastName: null, profilePictureUrl: null, lastSignInAt: null, },}; const fakeRoles: FakeRole[] = [ { slug: "admin", name: "Admin" }, { slug: "member", name: "Member" },]; // ---------------------------------------------------------------------------// The admin guard — mirrors handlers.ts// --------------------------------------------------------------------------- const requireAdmin = Effect.gen(function* () { const auth = yield* AuthContext; const workos = yield* WorkOSAuth; const memberships = yield* workos.listOrgMembers(auth.organizationId); const current = memberships.data.find((m: FakeMembership) => m.userId === auth.accountId); if (!current || current.role?.slug !== "admin") { return yield* new Forbidden(); }}); const provide = (auth: typeof adminAuth, workosOverrides: StubOverrides = {}) => Layer.mergeAll(Layer.succeed(AuthContext)(auth), stubWorkOS(workosOverrides)); const withMembers: StubOverrides = { listOrgMembers: () => Effect.succeed({ data: fakeMemberships }),}; // ---------------------------------------------------------------------------// Tests// --------------------------------------------------------------------------- describe("Org handlers", () => { describe("listMembers", () => { it.effect("returns members with isCurrentUser set correctly", () => Effect.gen(function* () { const auth = yield* AuthContext; const workos = yield* WorkOSAuth; const result = yield* workos.listOrgMembers(auth.organizationId); const members = yield* Effect.all( result.data.map((m: FakeMembership) => Effect.gen(function* () { const user = yield* workos.getUser(m.userId); return { id: m.id, email: user.email, role: m.role?.slug ?? "member", isCurrentUser: m.userId === auth.accountId, }; }), ), ); expect(members).toHaveLength(2); expect(members[0]).toMatchObject({ email: "admin@test.com", isCurrentUser: true, }); expect(members[1]).toMatchObject({ email: "member@test.com", isCurrentUser: false, }); }).pipe( Effect.provide( provide(adminAuth, { ...withMembers, getUser: (id: string) => Effect.succeed(fakeUsers[id]), }), ), ), ); }); describe("listRoles", () => { it.effect("returns available roles", () => Effect.gen(function* () { const auth = yield* AuthContext; const workos = yield* WorkOSAuth; const result = yield* workos.listOrgRoles(auth.organizationId); const roles = result.data.map((r: FakeRole) => ({ slug: r.slug, name: r.name, })); expect(roles).toEqual(fakeRoles); }).pipe( Effect.provide( provide(adminAuth, { listOrgRoles: () => Effect.succeed({ data: fakeRoles }), }), ), ), ); }); describe("requireAdmin", () => { it.effect("passes for admin user", () => requireAdmin.pipe(Effect.provide(provide(adminAuth, withMembers))), ); it.effect("rejects non-admin with Forbidden", () => Effect.gen(function* () { const error = yield* Effect.flip(requireAdmin); expect(error).toBeInstanceOf(Forbidden); }).pipe(Effect.provide(provide(memberAuth, withMembers))), ); }); describe("invite (admin-gated)", () => { it.effect("admin can invite", () => Effect.gen(function* () { yield* requireAdmin; const auth = yield* AuthContext; const workos = yield* WorkOSAuth; const result = yield* workos.sendInvitation({ email: "new@test.com", organizationId: auth.organizationId, }); expect(result.email).toBe("new@test.com"); }).pipe( Effect.provide( provide(adminAuth, { ...withMembers, sendInvitation: (p: { email: string }) => Effect.succeed({ id: "inv_1", email: p.email }), }), ), ), ); it.effect("member cannot invite", () => Effect.gen(function* () { const error = yield* Effect.flip( Effect.gen(function* () { yield* requireAdmin; const workos = yield* WorkOSAuth; yield* workos.sendInvitation({ email: "x", organizationId: "org_1", }); }), ); expect(error).toBeInstanceOf(Forbidden); }).pipe(Effect.provide(provide(memberAuth, withMembers))), ); }); describe("removeMember (admin-gated)", () => { it.effect("admin can remove", () => Effect.gen(function* () { yield* requireAdmin; const workos = yield* WorkOSAuth; yield* workos.deleteOrgMembership("mem_member"); }).pipe( Effect.provide( provide(adminAuth, { ...withMembers, deleteOrgMembership: () => Effect.void, }), ), ), ); it.effect("member cannot remove", () => Effect.gen(function* () { const error = yield* Effect.flip( Effect.gen(function* () { yield* requireAdmin; const workos = yield* WorkOSAuth; yield* workos.deleteOrgMembership("mem_admin"); }), ); expect(error).toBeInstanceOf(Forbidden); }).pipe(Effect.provide(provide(memberAuth, withMembers))), ); }); describe("updateMemberRole (admin-gated)", () => { it.effect("admin can change role", () => Effect.gen(function* () { yield* requireAdmin; const workos = yield* WorkOSAuth; yield* workos.updateOrgMembershipRole("mem_member", "admin"); }).pipe( Effect.provide( provide(adminAuth, { ...withMembers, updateOrgMembershipRole: () => Effect.void, }), ), ), ); it.effect("member cannot change role", () => Effect.gen(function* () { const error = yield* Effect.flip( Effect.gen(function* () { yield* requireAdmin; const workos = yield* WorkOSAuth; yield* workos.updateOrgMembershipRole("mem_admin", "member"); }), ); expect(error).toBeInstanceOf(Forbidden); }).pipe(Effect.provide(provide(memberAuth, withMembers))), ); });});
import { describe, it, expect } from "@effect/vitest";
import { Data, Effect, Layer } from "effect";
import { AuthContext } from "../auth/middleware";
import { WorkOSAuth, type WorkOSAuthService } from "../auth/workos";
import { Forbidden } from "./api";
// ---------------------------------------------------------------------------
// Stub factory — only implement what each test calls
// ---------------------------------------------------------------------------
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- test stub needs wide function types
type StubFn = (...args: never[]) => Effect.Effect<any>;
type StubOverrides = {
listOrgMembers?: StubFn;
getUser?: StubFn;
sendInvitation?: StubFn;
deleteOrgMembership?: StubFn;
updateOrgMembershipRole?: StubFn;
listOrgRoles?: StubFn;
};
class UnstubbedWorkOSMethod extends Data.TaggedError("UnstubbedWorkOSMethod")<{
method: string;
}> {}
const stubWorkOS = (overrides: StubOverrides = {}) =>
Layer.succeed(
WorkOSAuth,
new Proxy({} as WorkOSAuthService, {
get: (_target, prop) => {
if (typeof prop === "string" && prop in overrides) {
return overrides[prop as keyof StubOverrides];
}
return () =>
Effect.fail(
new UnstubbedWorkOSMethod({
method: typeof prop === "string" ? prop : (prop.description ?? "symbol"),
}),
);
},
}),
);
// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------
const adminAuth = {
accountId: "user_admin",
organizationId: "org_1",
email: "admin@test.com",
name: "Admin",
avatarUrl: null,
};
const memberAuth = {
accountId: "user_member",
organizationId: "org_1",
email: "member@test.com",
name: "Member",
avatarUrl: null,
};
type FakeMembership = {
id: string;
userId: string;
status: string;
role: { slug: string };
};
type FakeUser = {
email: string;
firstName: string | null;
lastName: string | null;
profilePictureUrl: string | null;
lastSignInAt: string | null;
};
type FakeRole = { slug: string; name: string };
const fakeMemberships: FakeMembership[] = [
{
id: "mem_admin",
userId: "user_admin",
status: "active",
role: { slug: "admin" },
},
{
id: "mem_member",
userId: "user_member",
status: "active",
role: { slug: "member" },
},
];
const fakeUsers: Record<string, FakeUser> = {
user_admin: {
email: "admin@test.com",
firstName: "Admin",
lastName: null,
profilePictureUrl: null,
lastSignInAt: "2026-04-09T00:00:00Z",
},
user_member: {
email: "member@test.com",
firstName: "Member",
lastName: null,
profilePictureUrl: null,
lastSignInAt: null,
},
};
const fakeRoles: FakeRole[] = [
{ slug: "admin", name: "Admin" },
{ slug: "member", name: "Member" },
];
// ---------------------------------------------------------------------------
// The admin guard — mirrors handlers.ts
// ---------------------------------------------------------------------------
const requireAdmin = Effect.gen(function* () {
const auth = yield* AuthContext;
const workos = yield* WorkOSAuth;
const memberships = yield* workos.listOrgMembers(auth.organizationId);
const current = memberships.data.find((m: FakeMembership) => m.userId === auth.accountId);
if (!current || current.role?.slug !== "admin") {
return yield* new Forbidden();
}
});
const provide = (auth: typeof adminAuth, workosOverrides: StubOverrides = {}) =>
Layer.mergeAll(Layer.succeed(AuthContext)(auth), stubWorkOS(workosOverrides));
const withMembers: StubOverrides = {
listOrgMembers: () => Effect.succeed({ data: fakeMemberships }),
};
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
describe("Org handlers", () => {
describe("listMembers", () => {
it.effect("returns members with isCurrentUser set correctly", () =>
Effect.gen(function* () {
const auth = yield* AuthContext;
const workos = yield* WorkOSAuth;
const result = yield* workos.listOrgMembers(auth.organizationId);
const members = yield* Effect.all(
result.data.map((m: FakeMembership) =>
Effect.gen(function* () {
const user = yield* workos.getUser(m.userId);
return {
id: m.id,
email: user.email,
role: m.role?.slug ?? "member",
isCurrentUser: m.userId === auth.accountId,
};
}),
),
);
expect(members).toHaveLength(2);
expect(members[0]).toMatchObject({
email: "admin@test.com",
isCurrentUser: true,
});
expect(members[1]).toMatchObject({
email: "member@test.com",
isCurrentUser: false,
});
}).pipe(
Effect.provide(
provide(adminAuth, {
...withMembers,
getUser: (id: string) => Effect.succeed(fakeUsers[id]),
}),
),
),
);
});
describe("listRoles", () => {
it.effect("returns available roles", () =>
Effect.gen(function* () {
const auth = yield* AuthContext;
const workos = yield* WorkOSAuth;
const result = yield* workos.listOrgRoles(auth.organizationId);
const roles = result.data.map((r: FakeRole) => ({
slug: r.slug,
name: r.name,
}));
expect(roles).toEqual(fakeRoles);
}).pipe(
Effect.provide(
provide(adminAuth, {
listOrgRoles: () => Effect.succeed({ data: fakeRoles }),
}),
),
),
);
});
describe("requireAdmin", () => {
it.effect("passes for admin user", () =>
requireAdmin.pipe(Effect.provide(provide(adminAuth, withMembers))),
);
it.effect("rejects non-admin with Forbidden", () =>
Effect.gen(function* () {
const error = yield* Effect.flip(requireAdmin);
expect(error).toBeInstanceOf(Forbidden);
}).pipe(Effect.provide(provide(memberAuth, withMembers))),
);
});
describe("invite (admin-gated)", () => {
it.effect("admin can invite", () =>
Effect.gen(function* () {
yield* requireAdmin;
const auth = yield* AuthContext;
const workos = yield* WorkOSAuth;
const result = yield* workos.sendInvitation({
email: "new@test.com",
organizationId: auth.organizationId,
});
expect(result.email).toBe("new@test.com");
}).pipe(
Effect.provide(
provide(adminAuth, {
...withMembers,
sendInvitation: (p: { email: string }) =>
Effect.succeed({ id: "inv_1", email: p.email }),
}),
),
),
);
it.effect("member cannot invite", () =>
Effect.gen(function* () {
const error = yield* Effect.flip(
Effect.gen(function* () {
yield* requireAdmin;
const workos = yield* WorkOSAuth;
yield* workos.sendInvitation({
email: "x",
organizationId: "org_1",
});
}),
);
expect(error).toBeInstanceOf(Forbidden);
}).pipe(Effect.provide(provide(memberAuth, withMembers))),
);
});
describe("removeMember (admin-gated)", () => {
it.effect("admin can remove", () =>
Effect.gen(function* () {
yield* requireAdmin;
const workos = yield* WorkOSAuth;
yield* workos.deleteOrgMembership("mem_member");
}).pipe(
Effect.provide(
provide(adminAuth, {
...withMembers,
deleteOrgMembership: () => Effect.void,
}),
),
),
);
it.effect("member cannot remove", () =>
Effect.gen(function* () {
const error = yield* Effect.flip(
Effect.gen(function* () {
yield* requireAdmin;
const workos = yield* WorkOSAuth;
yield* workos.deleteOrgMembership("mem_admin");
}),
);
expect(error).toBeInstanceOf(Forbidden);
}).pipe(Effect.provide(provide(memberAuth, withMembers))),
);
});
describe("updateMemberRole (admin-gated)", () => {
it.effect("admin can change role", () =>
Effect.gen(function* () {
yield* requireAdmin;
const workos = yield* WorkOSAuth;
yield* workos.updateOrgMembershipRole("mem_member", "admin");
}).pipe(
Effect.provide(
provide(adminAuth, {
...withMembers,
updateOrgMembershipRole: () => Effect.void,
}),
),
),
);
it.effect("member cannot change role", () =>
Effect.gen(function* () {
const error = yield* Effect.flip(
Effect.gen(function* () {
yield* requireAdmin;
const workos = yield* WorkOSAuth;
yield* workos.updateOrgMembershipRole("mem_admin", "member");
}),
);
expect(error).toBeInstanceOf(Forbidden);
}).pipe(Effect.provide(provide(memberAuth, withMembers))),
);
});
});