From a04d9d059fabeaf0737c5297fbba1a1bb440b1a0 Mon Sep 17 00:00:00 2001 From: HassanBahati Date: Mon, 20 Jan 2025 11:26:02 +0300 Subject: [PATCH 1/2] feat(react): add useUserGetIdTokenResultMutation --- packages/react/src/auth/index.ts | 2 +- .../userUserGetIdTokenResultMutation.test.tsx | 139 ++++++++++++++++++ .../auth/userUserGetIdTokenResultMutation.ts | 30 ++++ 3 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx create mode 100644 packages/react/src/auth/userUserGetIdTokenResultMutation.ts diff --git a/packages/react/src/auth/index.ts b/packages/react/src/auth/index.ts index 024d9a34..80fe8bb4 100644 --- a/packages/react/src/auth/index.ts +++ b/packages/react/src/auth/index.ts @@ -1,7 +1,7 @@ // useAuthStateReadyQuery (Auth) // useConfirmationResultConfirmMutation (ConfirmationResult) // useUserDeleteMutation (User) -// userUserGetIdTokenResultMutation (User) +export { userUserGetIdTokenResultMutation } from "./userUserGetIdTokenResultMutation"; // useUserGetIdTokenMutation (User) // useUserReloadMutation (User) // useVerifyPhoneNumberMutation (PhoneAuthProvider) diff --git a/packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx b/packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx new file mode 100644 index 00000000..858ad2df --- /dev/null +++ b/packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx @@ -0,0 +1,139 @@ +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { act, renderHook, waitFor } from "@testing-library/react"; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, +} from "firebase/auth"; +import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; +import { auth, wipeAuth } from "~/testing-utils"; +import { userUserGetIdTokenResultMutation } from "./userUserGetIdTokenResultMutation"; +import { queryClient, wrapper } from "../../utils"; + +describe("userUserGetIdTokenResultMutation", () => { + const email = "tqf@invertase.io"; + const password = "TanstackQueryFirebase#123"; + + beforeEach(async () => { + queryClient.clear(); + await wipeAuth(); + await createUserWithEmailAndPassword(auth, email, password); + }); + + afterEach(async () => { + vi.clearAllMocks(); + await auth.signOut(); + }); + + test("successfully retrieves ID token result with all properties", async () => { + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password + ); + const { user } = userCredential; + + const { result } = renderHook( + () => userUserGetIdTokenResultMutation(user), + { wrapper } + ); + + await act(async () => { + await result.current.mutateAsync(); + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + const tokenResult = result.current.data; + + // Verify all IdTokenResult properties exist and have correct types + expect(tokenResult?.authTime).toBeTypeOf("string"); + expect(tokenResult?.issuedAtTime).toBeTypeOf("string"); + expect(tokenResult?.expirationTime).toBeTypeOf("string"); + expect(tokenResult?.token).toBeTypeOf("string"); + expect(tokenResult?.claims).toBeTypeOf("object"); + expect( + typeof tokenResult?.signInProvider === "string" || + tokenResult?.signInProvider === null + ).toBe(true); + expect( + typeof tokenResult?.signInSecondFactor === "string" || + tokenResult?.signInSecondFactor === null + ).toBe(true); + }); + + test("can get token result with forceRefresh option", async () => { + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password + ); + const { user } = userCredential; + + const { result } = renderHook( + () => + userUserGetIdTokenResultMutation(user, { + auth: { forceRefresh: true }, + }), + { wrapper } + ); + + await act(async () => { + await result.current.mutateAsync(); + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + + const tokenResult = result.current.data; + expect(tokenResult?.token).toBeTypeOf("string"); + expect(tokenResult?.claims).toBeTypeOf("object"); + }); + + test("executes onSuccess callback with token result", async () => { + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password + ); + const { user } = userCredential; + const onSuccess = vi.fn(); + + const { result } = renderHook( + () => userUserGetIdTokenResultMutation(user, { onSuccess }), + { wrapper } + ); + + await act(async () => { + await result.current.mutateAsync(); + }); + + await waitFor(() => expect(onSuccess).toHaveBeenCalled()); + + const tokenResult = onSuccess.mock.calls[0][0]; + expect(tokenResult.token).toBeTypeOf("string"); + expect(tokenResult.claims).toBeTypeOf("object"); + expect(tokenResult.authTime).toBeTypeOf("string"); + }); + + test("verifies signInProvider for password authentication", async () => { + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password + ); + const { user } = userCredential; + + const { result } = renderHook( + () => userUserGetIdTokenResultMutation(user), + { wrapper } + ); + + await act(async () => { + await result.current.mutateAsync(); + }); + + await waitFor(() => expect(result.current.isSuccess).toBe(true)); + const tokenResult = result.current.data; + expect(tokenResult?.signInProvider).toBe("password"); + expect(tokenResult?.signInSecondFactor).toBeNull(); + }); +}); diff --git a/packages/react/src/auth/userUserGetIdTokenResultMutation.ts b/packages/react/src/auth/userUserGetIdTokenResultMutation.ts new file mode 100644 index 00000000..da810c57 --- /dev/null +++ b/packages/react/src/auth/userUserGetIdTokenResultMutation.ts @@ -0,0 +1,30 @@ +import { type UseMutationOptions, useMutation } from "@tanstack/react-query"; +import { + type User, + type AuthError, + getIdTokenResult, + type IdTokenResult, +} from "firebase/auth"; + +type AuthUseMutationOptions< + TData = unknown, + TError = Error, + TVariables = void +> = Omit, "mutationFn"> & { + auth?: { + forceRefresh?: boolean; + }; +}; + +export function userUserGetIdTokenResultMutation( + user: User, + options?: AuthUseMutationOptions +) { + const { auth, ...mutationOptions } = options || {}; + const forceRefresh = auth?.forceRefresh; + + return useMutation({ + ...mutationOptions, + mutationFn: () => getIdTokenResult(user, forceRefresh), + }); +} From 8ce0601a1cda906c0d43b1f9538ae034410512ba Mon Sep 17 00:00:00 2001 From: HassanBahati Date: Tue, 11 Feb 2025 17:28:05 +0300 Subject: [PATCH 2/2] refactor: move forceRefresh to mutation args --- packages/react/src/auth/index.ts | 2 +- .../auth/useUseGetIdTokenResultMutation.ts | 24 +++++++++++ ... useUserGetIdTokenResultMutation.test.tsx} | 43 ++++++++----------- .../auth/userUserGetIdTokenResultMutation.ts | 30 ------------- 4 files changed, 43 insertions(+), 56 deletions(-) create mode 100644 packages/react/src/auth/useUseGetIdTokenResultMutation.ts rename packages/react/src/auth/{userUserGetIdTokenResultMutation.test.tsx => useUserGetIdTokenResultMutation.test.tsx} (75%) delete mode 100644 packages/react/src/auth/userUserGetIdTokenResultMutation.ts diff --git a/packages/react/src/auth/index.ts b/packages/react/src/auth/index.ts index 3ade48f1..bd39f47b 100644 --- a/packages/react/src/auth/index.ts +++ b/packages/react/src/auth/index.ts @@ -1,7 +1,7 @@ // useAuthStateReadyQuery (Auth) // useConfirmationResultConfirmMutation (ConfirmationResult) // useUserDeleteMutation (User) -export { userUserGetIdTokenResultMutation } from "./userUserGetIdTokenResultMutation"; +export { useUserGetIdTokenResultMutation } from "./useUseGetIdTokenResultMutation"; // useUserGetIdTokenMutation (User) // useUserReloadMutation (User) // useVerifyPhoneNumberMutation (PhoneAuthProvider) diff --git a/packages/react/src/auth/useUseGetIdTokenResultMutation.ts b/packages/react/src/auth/useUseGetIdTokenResultMutation.ts new file mode 100644 index 00000000..4e6a6ad4 --- /dev/null +++ b/packages/react/src/auth/useUseGetIdTokenResultMutation.ts @@ -0,0 +1,24 @@ +import { type UseMutationOptions, useMutation } from "@tanstack/react-query"; +import { + type User, + type AuthError, + getIdTokenResult, + type IdTokenResult, +} from "firebase/auth"; + +type AuthUseMutationOptions< + TData = unknown, + TError = Error, + TVariables = void +> = Omit, "mutationFn">; + +export function useUserGetIdTokenResultMutation( + user: User, + options?: AuthUseMutationOptions +) { + return useMutation({ + ...options, + mutationFn: (forceRefresh?: boolean) => + getIdTokenResult(user, forceRefresh), + }); +} diff --git a/packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx b/packages/react/src/auth/useUserGetIdTokenResultMutation.test.tsx similarity index 75% rename from packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx rename to packages/react/src/auth/useUserGetIdTokenResultMutation.test.tsx index 858ad2df..daf289db 100644 --- a/packages/react/src/auth/userUserGetIdTokenResultMutation.test.tsx +++ b/packages/react/src/auth/useUserGetIdTokenResultMutation.test.tsx @@ -1,4 +1,3 @@ -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { act, renderHook, waitFor } from "@testing-library/react"; import { createUserWithEmailAndPassword, @@ -6,10 +5,10 @@ import { } from "firebase/auth"; import { afterEach, beforeEach, describe, expect, test, vi } from "vitest"; import { auth, wipeAuth } from "~/testing-utils"; -import { userUserGetIdTokenResultMutation } from "./userUserGetIdTokenResultMutation"; +import { useUserGetIdTokenResultMutation } from "./useUseGetIdTokenResultMutation"; import { queryClient, wrapper } from "../../utils"; -describe("userUserGetIdTokenResultMutation", () => { +describe("useUserGetIdTokenResultMutation", () => { const email = "tqf@invertase.io"; const password = "TanstackQueryFirebase#123"; @@ -24,7 +23,7 @@ describe("userUserGetIdTokenResultMutation", () => { await auth.signOut(); }); - test("successfully retrieves ID token result with all properties", async () => { + test("successfully retrieves ID token result with forceRefresh true", async () => { const userCredential = await signInWithEmailAndPassword( auth, email, @@ -32,13 +31,12 @@ describe("userUserGetIdTokenResultMutation", () => { ); const { user } = userCredential; - const { result } = renderHook( - () => userUserGetIdTokenResultMutation(user), - { wrapper } - ); + const { result } = renderHook(() => useUserGetIdTokenResultMutation(user), { + wrapper, + }); await act(async () => { - await result.current.mutateAsync(); + await result.current.mutateAsync(true); }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); @@ -61,7 +59,7 @@ describe("userUserGetIdTokenResultMutation", () => { ).toBe(true); }); - test("can get token result with forceRefresh option", async () => { + test("can get token result with forceRefresh false", async () => { const userCredential = await signInWithEmailAndPassword( auth, email, @@ -69,16 +67,12 @@ describe("userUserGetIdTokenResultMutation", () => { ); const { user } = userCredential; - const { result } = renderHook( - () => - userUserGetIdTokenResultMutation(user, { - auth: { forceRefresh: true }, - }), - { wrapper } - ); + const { result } = renderHook(() => useUserGetIdTokenResultMutation(user), { + wrapper, + }); await act(async () => { - await result.current.mutateAsync(); + await result.current.mutateAsync(false); }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); @@ -98,12 +92,12 @@ describe("userUserGetIdTokenResultMutation", () => { const onSuccess = vi.fn(); const { result } = renderHook( - () => userUserGetIdTokenResultMutation(user, { onSuccess }), + () => useUserGetIdTokenResultMutation(user, { onSuccess }), { wrapper } ); await act(async () => { - await result.current.mutateAsync(); + await result.current.mutateAsync(true); }); await waitFor(() => expect(onSuccess).toHaveBeenCalled()); @@ -122,13 +116,12 @@ describe("userUserGetIdTokenResultMutation", () => { ); const { user } = userCredential; - const { result } = renderHook( - () => userUserGetIdTokenResultMutation(user), - { wrapper } - ); + const { result } = renderHook(() => useUserGetIdTokenResultMutation(user), { + wrapper, + }); await act(async () => { - await result.current.mutateAsync(); + await result.current.mutateAsync(false); }); await waitFor(() => expect(result.current.isSuccess).toBe(true)); diff --git a/packages/react/src/auth/userUserGetIdTokenResultMutation.ts b/packages/react/src/auth/userUserGetIdTokenResultMutation.ts deleted file mode 100644 index da810c57..00000000 --- a/packages/react/src/auth/userUserGetIdTokenResultMutation.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { type UseMutationOptions, useMutation } from "@tanstack/react-query"; -import { - type User, - type AuthError, - getIdTokenResult, - type IdTokenResult, -} from "firebase/auth"; - -type AuthUseMutationOptions< - TData = unknown, - TError = Error, - TVariables = void -> = Omit, "mutationFn"> & { - auth?: { - forceRefresh?: boolean; - }; -}; - -export function userUserGetIdTokenResultMutation( - user: User, - options?: AuthUseMutationOptions -) { - const { auth, ...mutationOptions } = options || {}; - const forceRefresh = auth?.forceRefresh; - - return useMutation({ - ...mutationOptions, - mutationFn: () => getIdTokenResult(user, forceRefresh), - }); -}