Files
formbricks/apps/web/modules/ee/role-management/actions.test.ts
T
victorvhs017 665c7c6bf1 chore: add tests to api V1 (#5593)
Co-authored-by: Piyush Gupta <piyushguptaa2z123@gmail.com>
2025-05-05 05:38:16 +00:00

335 lines
13 KiB
TypeScript

import { getMembershipByUserIdOrganizationId } from "@/lib/membership/service";
import { getOrganization } from "@/lib/organization/service";
import { checkAuthorizationUpdated } from "@/lib/utils/action-client-middleware";
import { getRoleManagementPermission } from "@/modules/ee/license-check/lib/utils";
import {
TUpdateInviteAction,
checkRoleManagementPermission,
updateInviteAction,
updateMembershipAction,
} from "@/modules/ee/role-management/actions";
import { updateInvite } from "@/modules/ee/role-management/lib/invite";
import { updateMembership } from "@/modules/ee/role-management/lib/membership";
import { afterEach, describe, expect, test, vi } from "vitest";
import { AuthenticationError, OperationNotAllowedError, ValidationError } from "@formbricks/types/errors";
// Mock constants with getter functions to allow overriding in tests
let mockIsFormbricksCloud = false;
let mockDisableUserManagement = false;
vi.mock("@/lib/constants", () => ({
get IS_FORMBRICKS_CLOUD() {
return mockIsFormbricksCloud;
},
get DISABLE_USER_MANAGEMENT() {
return mockDisableUserManagement;
},
}));
vi.mock("@/lib/organization/service", () => ({
getOrganization: vi.fn(),
}));
vi.mock("@/lib/membership/service", () => ({
getMembershipByUserIdOrganizationId: vi.fn(),
}));
vi.mock("@/modules/ee/license-check/lib/utils", () => ({
getRoleManagementPermission: vi.fn(),
}));
vi.mock("@/lib/utils/action-client-middleware", () => ({
checkAuthorizationUpdated: vi.fn(),
}));
vi.mock("@/modules/ee/role-management/lib/invite", () => ({
updateInvite: vi.fn(),
}));
vi.mock("@/modules/ee/role-management/lib/membership", () => ({
updateMembership: vi.fn(),
}));
vi.mock("@/lib/utils/action-client", () => ({
authenticatedActionClient: {
schema: () => ({
action: (callback) => callback,
}),
},
}));
describe("Role Management Actions", () => {
afterEach(() => {
vi.resetAllMocks();
mockIsFormbricksCloud = false;
mockDisableUserManagement = false;
});
describe("checkRoleManagementPermission", () => {
test("throws error if organization not found", async () => {
vi.mocked(getOrganization).mockResolvedValue(null);
await expect(checkRoleManagementPermission("org-123")).rejects.toThrow("Organization not found");
});
test("throws error if role management is not allowed", async () => {
vi.mocked(getOrganization).mockResolvedValue({
billing: { plan: "free" },
} as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(false);
await expect(checkRoleManagementPermission("org-123")).rejects.toThrow(
new OperationNotAllowedError("Role management is not allowed for this organization")
);
});
test("succeeds if role management is allowed", async () => {
vi.mocked(getOrganization).mockResolvedValue({
billing: { plan: "pro" },
} as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
await expect(checkRoleManagementPermission("org-123")).resolves.not.toThrow();
});
});
describe("updateInviteAction", () => {
test("throws error if user is not a member of the organization", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue(null);
await expect(
updateInviteAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
inviteId: "invite-123",
organizationId: "org-123",
data: { role: "member" },
},
} as unknown as TUpdateInviteAction)
).rejects.toThrow(new AuthenticationError("User not a member of this organization"));
});
test("throws error if billing role is not allowed in self-hosted", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
await expect(
updateInviteAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
inviteId: "invite-123",
organizationId: "org-123",
data: { role: "billing" },
},
} as unknown as TUpdateInviteAction)
).rejects.toThrow(new ValidationError("Billing role is not allowed"));
});
test("allows billing role in cloud environment", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
mockIsFormbricksCloud = true;
vi.mocked(getOrganization).mockResolvedValue({ billing: { plan: "pro" } } as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
vi.mocked(updateInvite).mockResolvedValue({ id: "invite-123", role: "billing" } as any);
const result = await updateInviteAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
inviteId: "invite-123",
organizationId: "org-123",
data: { role: "billing" },
},
} as unknown as TUpdateInviteAction);
expect(result).toEqual({ id: "invite-123", role: "billing" });
});
test("throws error if manager tries to invite a role other than member", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "manager" } as any);
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
await expect(
updateInviteAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
inviteId: "invite-123",
organizationId: "org-123",
data: { role: "owner" },
},
} as unknown as TUpdateInviteAction)
).rejects.toThrow(new OperationNotAllowedError("Managers can only invite members"));
});
test("allows manager to invite a member", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "manager" } as any);
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
vi.mocked(getOrganization).mockResolvedValue({ billing: { plan: "pro" } } as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
vi.mocked(updateInvite).mockResolvedValue({ id: "invite-123", role: "member" } as any);
const result = await updateInviteAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
inviteId: "invite-123",
organizationId: "org-123",
data: { role: "member" },
},
} as unknown as TUpdateInviteAction);
expect(result).toEqual({ id: "invite-123", role: "member" });
expect(updateInvite).toHaveBeenCalledWith("invite-123", { role: "member" });
});
test("successful invite update as owner", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
vi.mocked(getOrganization).mockResolvedValue({ billing: { plan: "pro" } } as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
vi.mocked(updateInvite).mockResolvedValue({ id: "invite-123", role: "member" } as any);
const result = await updateInviteAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
inviteId: "invite-123",
organizationId: "org-123",
data: { role: "member" },
},
} as unknown as TUpdateInviteAction);
expect(result).toEqual({ id: "invite-123", role: "member" });
expect(updateInvite).toHaveBeenCalledWith("invite-123", { role: "member" });
});
});
describe("updateMembershipAction", () => {
test("throws error if user is not a member of the organization", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue(null);
await expect(
updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "member" },
},
} as any)
).rejects.toThrow(new AuthenticationError("User not a member of this organization"));
});
test("throws error if user management is disabled", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
mockDisableUserManagement = true;
await expect(
updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "member" },
},
} as any)
).rejects.toThrow(new OperationNotAllowedError("User management is disabled"));
});
test("throws error if billing role is not allowed in self-hosted", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
mockDisableUserManagement = false;
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
await expect(
updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "billing" },
},
} as any)
).rejects.toThrow(new ValidationError("Billing role is not allowed"));
});
test("allows billing role in cloud environment", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
mockDisableUserManagement = false;
mockIsFormbricksCloud = true;
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
vi.mocked(getOrganization).mockResolvedValue({ billing: { plan: "pro" } } as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
vi.mocked(updateMembership).mockResolvedValue({ id: "membership-123", role: "billing" } as any);
const result = await updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "billing" },
},
} as any);
expect(result).toEqual({ id: "membership-123", role: "billing" });
});
test("throws error if manager tries to assign a role other than member", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "manager" } as any);
mockDisableUserManagement = false;
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
await expect(
updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "owner" },
},
} as any)
).rejects.toThrow(new OperationNotAllowedError("Managers can only assign users to the member role"));
});
test("allows manager to assign member role", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "manager" } as any);
mockDisableUserManagement = false;
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
vi.mocked(getOrganization).mockResolvedValue({ billing: { plan: "pro" } } as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
vi.mocked(updateMembership).mockResolvedValue({ id: "membership-123", role: "member" } as any);
const result = await updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "member" },
},
} as any);
expect(result).toEqual({ id: "membership-123", role: "member" });
expect(updateMembership).toHaveBeenCalledWith("user-456", "org-123", { role: "member" });
});
test("successful membership update as owner", async () => {
vi.mocked(getMembershipByUserIdOrganizationId).mockResolvedValue({ role: "owner" } as any);
mockDisableUserManagement = false;
vi.mocked(checkAuthorizationUpdated).mockResolvedValue(true);
vi.mocked(getOrganization).mockResolvedValue({ billing: { plan: "pro" } } as any);
vi.mocked(getRoleManagementPermission).mockResolvedValue(true);
vi.mocked(updateMembership).mockResolvedValue({ id: "membership-123", role: "member" } as any);
const result = await updateMembershipAction({
ctx: { user: { id: "user-123" } },
parsedInput: {
userId: "user-456",
organizationId: "org-123",
data: { role: "member" },
},
} as any);
expect(result).toEqual({ id: "membership-123", role: "member" });
expect(updateMembership).toHaveBeenCalledWith("user-456", "org-123", { role: "member" });
});
});
});