UNPKG

next-auth

Version:

Authentication for Next.js

291 lines (240 loc) 7.01 kB
import { useState } from "react" import userEvent from "@testing-library/user-event" import { render, screen, waitFor } from "@testing-library/react" import logger from "../../utils/logger" import { server, mockCredentialsResponse, mockEmailResponse, mockGithubResponse, } from "./helpers/mocks" import { signIn } from "../../react" import { rest } from "msw" const { location } = window jest.mock("../../utils/logger", () => ({ __esModule: true, default: { warn: jest.fn(), debug: jest.fn(), error: jest.fn(), }, proxyLogger(logger) { return logger }, })) beforeAll(() => { server.listen() let _href = window.location.href // Allows to mutate `window.location`... delete window.location window.location = { reload: jest.fn(), } Object.defineProperty(window.location, "href", { get: () => _href, // whatwg-fetch or whatwg-url does not seem to work with relative URLs set: (href) => { _href = href.startsWith("/") ? `http://localhost${href}` : href return _href }, }) }) beforeEach(() => { jest.clearAllMocks() server.resetHandlers() }) afterAll(() => { window.location = location server.close() }) const callbackUrl = "https://redirects/to" test.each` provider | type ${""} | ${"no"} ${"foo"} | ${"unknown"} `( "if $type provider, it redirects to the default sign-in page", async ({ provider }) => { render(<SignInFlow providerId={provider} callbackUrl={callbackUrl} />) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).toBe( `http://localhost/api/auth/signin?${new URLSearchParams({ callbackUrl, })}` ) }) } ) test.each` provider | type ${""} | ${"no"} ${"foo"} | ${"unknown"} `( "if $type provider supplied and no callback URL, redirects using the current location", async ({ provider }) => { render(<SignInFlow providerId={provider} />) const callbackUrl = window.location.href userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).toBe( `http://localhost/api/auth/signin?${new URLSearchParams({ callbackUrl, })}` ) }) } ) test.each` provider | mockUrl ${`email`} | ${mockEmailResponse.url} ${`credentials`} | ${mockCredentialsResponse.url} `( "$provider provider redirects if `redirect` is `true`", async ({ provider, mockUrl }) => { render(<SignInFlow providerId={provider} redirect={true} />) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).toBe(mockUrl) }) } ) test("redirection can't be stopped using an oauth provider", async () => { render( <SignInFlow providerId="github" callbackUrl={callbackUrl} redirect={false} /> ) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).toBe(mockGithubResponse.url) }) }) test("redirection can be stopped using the 'credentials' provider", async () => { render( <SignInFlow providerId="credentials" callbackUrl={callbackUrl} redirect={false} /> ) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).not.toBe(mockCredentialsResponse.url) expect(screen.getByTestId("signin-result").textContent).not.toBe( "no response" ) }) // snapshot the expected return shape from `signIn` expect(JSON.parse(screen.getByTestId("signin-result").textContent)) .toMatchInlineSnapshot(` Object { "error": null, "ok": true, "status": 200, "url": "https://path/to/credentials/url", } `) }) test("redirection can be stopped using the 'email' provider", async () => { render( <SignInFlow providerId="email" callbackUrl={callbackUrl} redirect={false} /> ) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).not.toBe(mockEmailResponse.url) expect(screen.getByTestId("signin-result").textContent).not.toBe( "no response" ) }) // snapshot the expected return shape from `signIn` oauth expect(JSON.parse(screen.getByTestId("signin-result").textContent)) .toMatchInlineSnapshot(` Object { "error": null, "ok": true, "status": 200, "url": "https://path/to/email/url", } `) }) test("if callback URL contains a hash we force a window reload when re-directing", async () => { const mockUrlWithHash = "https://path/to/email/url#foo-bar-baz" server.use( rest.post("*/api/auth/signin/email", (req, res, ctx) => { return res( ctx.status(200), ctx.json({ ...mockEmailResponse, url: mockUrlWithHash, }) ) }) ) render(<SignInFlow providerId="email" callbackUrl={mockUrlWithHash} />) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).toBe(mockUrlWithHash) // the browser will not refresh the page if the redirect URL contains a hash, hence we force it on the client, see #1289 expect(window.location.reload).toHaveBeenCalledTimes(1) }) }) test("params are propagated to the signin URL when supplied", async () => { let matchedParams = "" const authParams = "foo=bar&bar=foo" server.use( rest.post("*/auth/signin/github", (req, res, ctx) => { matchedParams = req.url.search return res(ctx.status(200), ctx.json(mockGithubResponse)) }) ) render(<SignInFlow providerId="github" authorizationParams={authParams} />) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(matchedParams).toEqual(`?${authParams}`) }) }) test("when it fails to fetch the providers, it redirected back to signin page", async () => { const errorMsg = "Error when retrieving providers" server.use( rest.get("*/api/auth/providers", (req, res, ctx) => res(ctx.status(500), ctx.json(errorMsg)) ) ) render(<SignInFlow providerId="github" />) userEvent.click(screen.getByRole("button")) await waitFor(() => { expect(window.location.href).toBe(`http://localhost/api/auth/error`) expect(logger.error).toHaveBeenCalledTimes(1) expect(logger.error).toBeCalledWith("CLIENT_FETCH_ERROR", { error: "Error when retrieving providers", url: "/api/auth/providers", }) }) }) function SignInFlow({ providerId, callbackUrl, redirect = true, authorizationParams = {}, }) { const [response, setResponse] = useState(null) async function handleSignIn() { const result = await signIn( providerId, { callbackUrl, redirect }, authorizationParams ) setResponse(result) } return ( <> <p data-testid="signin-result"> {response ? JSON.stringify(response) : "no response"} </p> <button onClick={handleSignIn}>Sign in</button> </> ) }