fix: unit tests and logging

This commit is contained in:
Piyush Gupta
2025-07-01 13:23:47 +05:30
parent ddb95b7cbb
commit b655e649ac
8 changed files with 41 additions and 42 deletions

View File

@@ -94,6 +94,7 @@ describe("LandingSidebar component", () => {
organizationId: "o1",
redirect: true,
callbackUrl: "/auth/login",
clearEnvironmentId: true,
});
});
});

View File

@@ -221,7 +221,6 @@ describe("MainNavigation", () => {
vi.mocked(useSignOut).mockReturnValue({ signOut: mockSignOut });
// Set up localStorage spy on the mocked localStorage
const removeItemSpy = vi.spyOn(window.localStorage, "removeItem");
render(<MainNavigation {...defaultProps} />);
@@ -243,23 +242,18 @@ describe("MainNavigation", () => {
const logoutButton = screen.getByText("common.logout");
await userEvent.click(logoutButton);
// Verify localStorage.removeItem is called with the correct key
expect(removeItemSpy).toHaveBeenCalledWith("formbricks-environment-id");
expect(mockSignOut).toHaveBeenCalledWith({
reason: "user_initiated",
redirectUrl: "/auth/login",
organizationId: "org1",
redirect: false,
callbackUrl: "/auth/login",
clearEnvironmentId: true,
});
await waitFor(() => {
expect(mockRouterPush).toHaveBeenCalledWith("/auth/login");
});
// Clean up spy
removeItemSpy.mockRestore();
});
test("handles organization switching", async () => {

View File

@@ -163,12 +163,26 @@ export const removeAvatarAction = authenticatedActionClient.schema(ZRemoveAvatar
)
);
export const resetPasswordAction = authenticatedActionClient.action(async ({ ctx }) => {
if (ctx.user.identityProvider !== "email") {
throw new OperationNotAllowedError("Password reset is not allowed for SSO users");
}
export const resetPasswordAction = authenticatedActionClient.schema(z.object({})).action(
withAuditLogging(
"passwordReset",
"user",
async ({
ctx,
parsedInput: _,
}: {
ctx: AuthenticatedActionClientCtx;
parsedInput: Record<string, any>;
}) => {
if (ctx.user.identityProvider !== "email") {
throw new OperationNotAllowedError("Password reset is not allowed for SSO users");
}
await sendForgotPasswordEmail(ctx.user);
await sendForgotPasswordEmail(ctx.user);
return { success: true };
});
ctx.auditLoggingCtx.userId = ctx.user.id;
return { success: true };
}
)
);

View File

@@ -4,7 +4,7 @@ import userEvent from "@testing-library/user-event";
import toast from "react-hot-toast";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
import { TUser } from "@formbricks/types/user";
import { updateUserAction } from "../actions";
import { resetPasswordAction, updateUserAction } from "../actions";
import { EditProfileDetailsForm } from "./EditProfileDetailsForm";
const mockUser = {
@@ -38,6 +38,7 @@ beforeEach(() => {
vi.mock("@/app/(app)/environments/[environmentId]/settings/(account)/profile/actions", () => ({
updateUserAction: vi.fn(),
resetPasswordAction: vi.fn(),
}));
vi.mock("@/modules/auth/forgot-password/actions", () => ({
@@ -144,7 +145,7 @@ describe("EditProfileDetailsForm", () => {
});
test("reset password button works", async () => {
vi.mocked(forgotPasswordAction).mockResolvedValue(undefined);
vi.mocked(resetPasswordAction).mockResolvedValue({ data: { success: true } });
render(
<EditProfileDetailsForm
@@ -158,8 +159,9 @@ describe("EditProfileDetailsForm", () => {
await userEvent.click(resetButton);
await waitFor(() => {
expect(forgotPasswordAction).toHaveBeenCalledWith({ email: mockUser.email });
expect(resetPasswordAction).toHaveBeenCalled();
});
await waitFor(() => {
expect(toast.success).toHaveBeenCalledWith("auth.forgot-password.email-sent.heading");
});
@@ -167,7 +169,7 @@ describe("EditProfileDetailsForm", () => {
test("reset password button handles error correctly", async () => {
const errorMessage = "Reset failed";
vi.mocked(forgotPasswordAction).mockRejectedValue(new Error(errorMessage));
vi.mocked(resetPasswordAction).mockResolvedValue({ serverError: errorMessage });
render(
<EditProfileDetailsForm
@@ -181,12 +183,16 @@ describe("EditProfileDetailsForm", () => {
await userEvent.click(resetButton);
await waitFor(() => {
expect(forgotPasswordAction).toHaveBeenCalledWith({ email: mockUser.email });
expect(resetPasswordAction).toHaveBeenCalled();
});
await waitFor(() => {
expect(toast.error).toHaveBeenCalledWith(errorMessage);
});
});
test("reset password button shows loading state", async () => {
vi.mocked(forgotPasswordAction).mockImplementation(() => new Promise(() => {})); // Never resolves
vi.mocked(resetPasswordAction).mockImplementation(() => new Promise(() => {})); // Never resolves
render(
<EditProfileDetailsForm

View File

@@ -132,8 +132,7 @@ export const EditProfileDetailsForm = ({
const handleResetPassword = async () => {
setIsResettingPassword(true);
const result = await resetPasswordAction();
const result = await resetPasswordAction({});
if (result?.data) {
toast.success(t("auth.forgot-password.email-sent.heading"));

View File

@@ -1,4 +1,3 @@
import { FORMBRICKS_ENVIRONMENT_ID_LS } from "@/lib/localStorage";
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, test, vi } from "vitest";
import { TOrganization } from "@formbricks/types/organizations";
@@ -100,8 +99,6 @@ describe("DeleteAccountModal", () => {
/>
);
const removeItemSpy = vi.spyOn(window.localStorage, "removeItem");
const input = screen.getByTestId("deleteAccountConfirmation");
fireEvent.change(input, { target: { value: mockUser.email } });
@@ -113,8 +110,8 @@ describe("DeleteAccountModal", () => {
expect(mockSignOut).toHaveBeenCalledWith({
reason: "account_deletion",
redirect: false, // Updated to match new implementation
clearEnvironmentId: true,
});
expect(removeItemSpy).toHaveBeenCalledWith(FORMBRICKS_ENVIRONMENT_ID_LS);
expect(window.location.replace).toHaveBeenCalledWith("/auth/login");
expect(mockSetOpen).toHaveBeenCalledWith(false);
});
@@ -151,15 +148,13 @@ describe("DeleteAccountModal", () => {
const form = screen.getByTestId("deleteAccountForm");
fireEvent.submit(form);
const removeItemSpy = vi.spyOn(window.localStorage, "removeItem");
await waitFor(() => {
expect(deleteUserAction).toHaveBeenCalled();
expect(mockSignOut).toHaveBeenCalledWith({
reason: "account_deletion",
redirect: false, // Updated to match new implementation
clearEnvironmentId: true,
});
expect(removeItemSpy).toHaveBeenCalledWith(FORMBRICKS_ENVIRONMENT_ID_LS);
expect(window.location.replace).toHaveBeenCalledWith(
"https://app.formbricks.com/s/clri52y3z8f221225wjdhsoo2"
);

View File

@@ -50,6 +50,7 @@ export const ZAuditAction = z.enum([
"twoFactorRequired",
"emailVerificationAttempted",
"userSignedOut",
"passwordReset",
]);
export const ZActor = z.enum(["user", "api", "system"]);
export const ZAuditStatus = z.enum(["success", "failure"]);

View File

@@ -3,14 +3,6 @@ import { render } from "@testing-library/react";
import { type MockedFunction, beforeEach, describe, expect, test, vi } from "vitest";
import { ClientLogout } from "./index";
// Mock the localStorage
const mockRemoveItem = vi.fn();
Object.defineProperty(window, "localStorage", {
value: {
removeItem: mockRemoveItem,
},
});
// Mock next-auth/react
const mockSignOut = vi.fn();
vi.mock("@/modules/auth/hooks/use-sign-out", () => ({
@@ -37,6 +29,7 @@ describe("ClientLogout", () => {
redirectUrl: "/auth/login",
redirect: false,
callbackUrl: "/auth/login",
clearEnvironmentId: true,
});
});
@@ -50,14 +43,10 @@ describe("ClientLogout", () => {
redirectUrl: "/auth/login",
redirect: false,
callbackUrl: "/auth/login",
clearEnvironmentId: true,
});
});
test("removes environment ID from localStorage", () => {
render(<ClientLogout />);
expect(mockRemoveItem).toHaveBeenCalledWith("formbricks-environment-id");
});
test("renders null", () => {
const { container } = render(<ClientLogout />);
expect(container.firstChild).toBeNull();