From 47f01993e8a2574abea4b909a00a2cb0b0de8aec Mon Sep 17 00:00:00 2001
From: HassanBahati <mukisabahati@gmail.com>
Date: Mon, 27 Jan 2025 10:56:24 +0300
Subject: [PATCH] feat(react/auth): add
 useMultiFactorResolverResolveSignInMutation

---
 packages/react/src/auth/index.ts              |   2 +-
 ...ctorResolverResolveSignInMutation.test.tsx | 244 ++++++++++++++++++
 ...ultiFactorResolverResolveSignInMutation.ts |  28 ++
 3 files changed, 273 insertions(+), 1 deletion(-)
 create mode 100644 packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.test.tsx
 create mode 100644 packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.ts

diff --git a/packages/react/src/auth/index.ts b/packages/react/src/auth/index.ts
index 024d9a34..a5f738f9 100644
--- a/packages/react/src/auth/index.ts
+++ b/packages/react/src/auth/index.ts
@@ -8,7 +8,7 @@
 // useMultiFactorUserEnrollMutation (MultiFactorUser)
 // useMultiFactorUserUnenrollMutation (MultiFactorUser)
 // useMultiFactorUserGetSessionMutation (MultiFactorUser)
-// useMultiFactorResolverResolveSignInMutation (MultiFactorResolver)
+export { useMultiFactorResolverResolveSignInMutation } from "./useMultiFactorResolverResolveSignInMutation";
 // useApplyActionCodeMutation
 // useCheckActionCodeMutation
 // useConfirmPasswordResetMutation
diff --git a/packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.test.tsx b/packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.test.tsx
new file mode 100644
index 00000000..978bfb0d
--- /dev/null
+++ b/packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.test.tsx
@@ -0,0 +1,244 @@
+import { act, renderHook, waitFor } from "@testing-library/react";
+import { afterEach, describe, expect, test, vi, beforeEach } from "vitest";
+import {
+  type MultiFactorError,
+  MultiFactorResolver,
+  getMultiFactorResolver,
+} from "firebase/auth";
+import { auth, wipeAuth } from "~/testing-utils";
+import { useMultiFactorResolverResolveSignInMutation } from "./useMultiFactorResolverResolveSignInMutation";
+import { queryClient, wrapper } from "../../utils";
+
+vi.mock("firebase/auth", async () => {
+  const actual = await vi.importActual("firebase/auth");
+  return {
+    ...actual,
+    getMultiFactorResolver: vi.fn(),
+  };
+});
+
+const createMockMultiFactorError = (
+  operationType: "signIn" | "link" | "reauthenticate" = "signIn",
+  customData: Partial<{
+    email: string;
+    phoneNumber: string;
+    tenantId: string;
+  }> = {}
+) => {
+  const mockError = Object.assign(
+    new Error("Multi-factor authentication required"),
+    {
+      name: "MultiFactorError",
+      code: "auth/multi-factor-auth-required",
+      customData: {
+        appName: "[DEFAULT]",
+        operationType,
+        ...customData,
+      },
+    }
+  );
+
+  return mockError as unknown as MultiFactorError;
+};
+
+describe("useMultiFactorResolverResolveSignInMutation", () => {
+  beforeEach(async () => {
+    queryClient.clear();
+    await wipeAuth();
+  });
+
+  afterEach(async () => {
+    vi.clearAllMocks();
+  });
+
+  test("returns multi-factor resolver for sign-in operation", async () => {
+    const mockResolver = {
+      hints: [],
+      session: "mock-session",
+      resolveSignIn: vi.fn(),
+    } as unknown as MultiFactorResolver;
+
+    const mockMultiFactorError = createMockMultiFactorError("signIn", {
+      email: "test@example.com",
+    });
+
+    vi.mocked(getMultiFactorResolver).mockResolvedValueOnce(mockResolver);
+
+    const { result } = renderHook(
+      () => useMultiFactorResolverResolveSignInMutation(auth),
+      { wrapper }
+    );
+
+    let resolver;
+
+    await act(async () => {
+      resolver = await result.current.mutateAsync(mockMultiFactorError);
+    });
+
+    expect(getMultiFactorResolver).toHaveBeenCalledWith(
+      auth,
+      mockMultiFactorError
+    );
+    expect(resolver).toEqual(mockResolver);
+  });
+
+  test("returns multi-factor resolver for link operation", async () => {
+    const mockResolver = {
+      hints: [],
+      session: "mock-session",
+      resolveSignIn: vi.fn(),
+    } as unknown as MultiFactorResolver;
+
+    const mockMultiFactorError = createMockMultiFactorError("link", {
+      email: "test@example.com",
+    });
+
+    vi.mocked(getMultiFactorResolver).mockResolvedValueOnce(mockResolver);
+
+    const { result } = renderHook(
+      () => useMultiFactorResolverResolveSignInMutation(auth),
+      { wrapper }
+    );
+
+    let resolver;
+
+    await act(async () => {
+      resolver = await result.current.mutateAsync(mockMultiFactorError);
+    });
+
+    expect(getMultiFactorResolver).toHaveBeenCalledWith(
+      auth,
+      mockMultiFactorError
+    );
+    expect(resolver).toEqual(mockResolver);
+  });
+
+  test("returns multi-factor resolver for reauthenticate operation", async () => {
+    const mockResolver = {
+      hints: [],
+      session: "mock-session",
+      resolveSignIn: vi.fn(),
+    } as unknown as MultiFactorResolver;
+
+    const mockMultiFactorError = createMockMultiFactorError("reauthenticate", {
+      email: "test@example.com",
+    });
+
+    vi.mocked(getMultiFactorResolver).mockResolvedValueOnce(mockResolver);
+
+    const { result } = renderHook(
+      () => useMultiFactorResolverResolveSignInMutation(auth),
+      { wrapper }
+    );
+
+    let resolver;
+
+    await act(async () => {
+      resolver = await result.current.mutateAsync(mockMultiFactorError);
+    });
+
+    expect(getMultiFactorResolver).toHaveBeenCalledWith(
+      auth,
+      mockMultiFactorError
+    );
+    expect(resolver).toEqual(mockResolver);
+  });
+
+  test("handles phone number in custom data", async () => {
+    const mockResolver = {
+      hints: [],
+      session: "mock-session",
+      resolveSignIn: vi.fn(),
+    } as unknown as MultiFactorResolver;
+
+    const mockMultiFactorError = createMockMultiFactorError("signIn", {
+      phoneNumber: "+1234567890",
+    });
+
+    vi.mocked(getMultiFactorResolver).mockResolvedValueOnce(mockResolver);
+
+    const { result } = renderHook(
+      () => useMultiFactorResolverResolveSignInMutation(auth),
+      { wrapper }
+    );
+
+    let resolver;
+
+    await act(async () => {
+      resolver = await result.current.mutateAsync(mockMultiFactorError);
+    });
+
+    expect(getMultiFactorResolver).toHaveBeenCalledWith(
+      auth,
+      mockMultiFactorError
+    );
+    expect(resolver).toEqual(mockResolver);
+  });
+
+  test("executes onSuccess callback with correct parameters", async () => {
+    const mockResolver = {
+      hints: [],
+      session: "mock-session",
+      resolveSignIn: vi.fn(),
+    } as unknown as MultiFactorResolver;
+
+    const onSuccess = vi.fn();
+    const mockMultiFactorError = createMockMultiFactorError("signIn", {
+      email: "test@example.com",
+      tenantId: "mock-tenant-id",
+    });
+
+    vi.mocked(getMultiFactorResolver).mockResolvedValueOnce(mockResolver);
+
+    const { result } = renderHook(
+      () => useMultiFactorResolverResolveSignInMutation(auth, { onSuccess }),
+      { wrapper }
+    );
+
+    await act(async () => {
+      await result.current.mutateAsync(mockMultiFactorError);
+    });
+
+    await waitFor(() => {
+      expect(onSuccess).toHaveBeenCalledWith(
+        mockResolver,
+        mockMultiFactorError,
+        undefined
+      );
+      expect(onSuccess).toHaveBeenCalledTimes(1);
+    });
+  });
+
+  test("executes onError callback with network error", async () => {
+    const onError = vi.fn();
+    const networkError = new Error("Network error");
+    networkError.name = "NetworkError";
+
+    vi.mocked(getMultiFactorResolver).mockRejectedValueOnce(networkError);
+
+    const mockMultiFactorError = createMockMultiFactorError("signIn");
+
+    const { result } = renderHook(
+      () => useMultiFactorResolverResolveSignInMutation(auth, { onError }),
+      { wrapper }
+    );
+
+    await act(async () => {
+      try {
+        await result.current.mutateAsync(mockMultiFactorError);
+      } catch (error) {
+        expect(error).toEqual(networkError);
+      }
+    });
+
+    await waitFor(() => {
+      expect(onError).toHaveBeenCalledWith(
+        networkError,
+        mockMultiFactorError,
+        undefined
+      );
+      expect(onError).toHaveBeenCalledTimes(1);
+      expect(result.current.error).toEqual(networkError);
+    });
+  });
+});
diff --git a/packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.ts b/packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.ts
new file mode 100644
index 00000000..5998a684
--- /dev/null
+++ b/packages/react/src/auth/useMultiFactorResolverResolveSignInMutation.ts
@@ -0,0 +1,28 @@
+import { type UseMutationOptions, useMutation } from "@tanstack/react-query";
+import {
+  type Auth,
+  type AuthError,
+  type MultiFactorError,
+  getMultiFactorResolver,
+  MultiFactorResolver,
+} from "firebase/auth";
+
+type AuthUseMutationOptions<
+  TData = unknown,
+  TError = Error,
+  TVariables = void
+> = Omit<UseMutationOptions<TData, TError, TVariables>, "mutationFn">;
+
+export function useMultiFactorResolverResolveSignInMutation(
+  auth: Auth,
+  options?: AuthUseMutationOptions<
+    MultiFactorResolver,
+    AuthError,
+    MultiFactorError
+  >
+) {
+  return useMutation<MultiFactorResolver, AuthError, MultiFactorError>({
+    ...options,
+    mutationFn: async (error) => getMultiFactorResolver(auth, error),
+  });
+}