refactor(fe-auth): move login login to useLogin query hook

docs: additional documentation for auth hooks

wip: useCurrentUser hook documentation and coverage
This commit is contained in:
Marc 2024-01-04 11:28:04 -05:00
parent 39d71e2032
commit 68f34deeee
Signed by: marc
GPG key ID: 048E042F22B5DC79
4 changed files with 123 additions and 23 deletions

View file

@ -1,5 +1,4 @@
import React from "react"
import { useMutation } from "@tanstack/react-query"
import Typography from "@mui/material/Typography"
import Box from "@mui/material/Box"
@ -11,29 +10,14 @@ import Button from "@mui/material/Button"
import Link from "@mui/material/Link"
import Alert from "@mui/material/Alert"
import { useLogin } from "../../queries/auth"
import axiosWithDefaults from "../../axios"
import TextInput from "../TextInput"
import { useLocationContext } from "../../contexts/LocationContext"
function LoginView() {
const [emailAddress, setEmailAddress] = React.useState<string>("")
const [password, setPassword] = React.useState<string>("")
const { navigate } = useLocationContext()
const { mutate, isError, isPending } = useMutation({
mutationFn: async ({
email,
password,
}: { email: string; password: string }) => {
return axiosWithDefaults.post("/auth/session/", {
username: email,
password,
})
},
onSuccess: (response) => {
navigate("/")
},
})
const { login, isError, isPending } = useLogin()
const emailField = React.useMemo(
() => (
@ -68,8 +52,8 @@ function LoginView() {
const onLoginClick = React.useCallback(() => {
if (!isFormValid) return
mutate({ email: emailAddress, password })
}, [mutate, emailAddress, password, isFormValid])
login({ email: emailAddress, password })
}, [login, emailAddress, password, isFormValid])
return (
<Box sx={{ display: "flex", justifyContent: "center", width: "100%" }}>

View file

@ -5,7 +5,7 @@
* affect the user's session.
*
*/
import { useMutation } from "@tanstack/react-query"
import { useQueryClient, useMutation } from "@tanstack/react-query"
import { useLocationContext } from "../contexts/LocationContext"
import axiosWithDefaults from "../axios"
@ -15,14 +15,19 @@ import axiosWithDefaults from "../axios"
* Using `logout` will instruct the application to invalidate
* the current authentication token and will redirect the user
* to the login page.
*
* On success, the current-user query is invalidated so that the
* application is made aware that the user is no longer authenticated.
*/
function useLogout() {
const { navigate } = useLocationContext()
const queryClient = useQueryClient()
const logoutMutation = useMutation({
mutationFn: async () => {
return axiosWithDefaults.delete("/auth/session/")
},
onSuccess: () => {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: ["current-user"] })
navigate("/login")
},
})
@ -36,8 +41,37 @@ function useLogout() {
}
}
export { useLogout }
/*
* Handles the log-in interaction.
*
* Using `login` will send a request to create a session and on success,
* refetch the current user data.
*/
function useLogin() {
const queryClient = useQueryClient()
const { navigate } = useLocationContext()
const { mutate, isError, failureReason, isPending } = useMutation({
mutationFn: async ({
email,
password,
}: { email: string; password: string }) => {
return axiosWithDefaults.post("/auth/session/", {
username: email,
password,
})
},
onSuccess: async () => {
await queryClient.refetchQueries({ queryKey: ["current-user"] })
navigate("/")
},
})
return { login: mutate, isError, isPending }
}
export { useLogout, useLogin }
export default {
useLogout,
useLogin,
}

View file

@ -0,0 +1,54 @@
import React from "react"
import { describe, it, expect, vi } from "vitest"
import { renderHook, waitFor } from "@testing-library/react"
import { QueryClientProvider, QueryClient } from "@tanstack/react-query"
import AxiosMockAdapter from "axios-mock-adapter"
import axiosWithDefaults from "../axios"
import { useCurrentUser } from "./user"
function WithProviders({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={new QueryClient()}>
{children}
</QueryClientProvider>
)
}
describe("useCurrentUser", () => {
it("sends a request to the currentUser api", async () => {
const axios = new AxiosMockAdapter(axiosWithDefaults)
axios.onGet("/auth/user/").reply(204)
const { result } = renderHook(() => useCurrentUser(), {
wrapper: WithProviders,
})
await waitFor(() => expect(axios.history.get.length).toEqual(1))
const getRequest = axios.history.get[0]
expect(getRequest.url).toEqual("/auth/user/")
})
it.each`
isAuthenticated | returnCode
${false} | ${403}
${true} | ${200}
`(
"sets isAuthenticated ($isAuthenticated) when the api returns $returnCode",
async ({ isAuthenticated, returnCode }) => {
const axios = new AxiosMockAdapter(axiosWithDefaults)
axios.onGet("/auth/user/").reply(returnCode, { status: returnCode })
const { result } = renderHook(() => useCurrentUser(), {
wrapper: WithProviders,
})
await waitFor(() => expect(result.current.isLoading).toBe(false))
expect(result.current.isAuthenticated).toBe(isAuthenticated)
},
)
})

View file

@ -0,0 +1,28 @@
import { useQuery } from "@tanstack/react-query"
import axiosWithDefaults from "../axios"
/*
* Current user data fetch.
*
* This hook fetches data about the currently-authenticated user
* if there is one and returns basic information about them as well
* as a `isAuthenticated` flag that determines if we are logged in or not.
*/
function useCurrentUser() {
const { data, isLoading, isError, isSuccess } = useQuery({
queryKey: ["current-user"],
queryFn: async () => {
return axiosWithDefaults.get("/auth/user/")
},
retry: false,
})
const isAuthenticated = !isLoading && isSuccess && data.status === 200
return { currentUser: data, isSuccess, isError, isLoading, isAuthenticated }
}
export { useCurrentUser }
export default { useCurrentUser }