UNPKG

@waelhabbaldev/next-jwt-auth

Version:

A secure, lightweight JWT authentication solution for Next.js, providing access and refresh token handling, middleware support, and easy React integration.

105 lines (95 loc) 3.47 kB
import { describe, it, expect, mock, beforeEach } from "bun:test"; import { protectPage, protectAction, protectApi } from "./protection"; import { NotAuthenticatedError, ForbiddenError, IdentityForbiddenError, } from "../common/errors"; import { mockUser } from "../common/authentication.test-helpers"; import { EffectiveAuthConfig, SessionFailureReason } from "./authentication"; import { NextResponse } from "next/server"; import { AuthSession, UserIdentity } from "../common/types"; const mockRedirect = mock((path: string) => { throw new Error(`NEXT_REDIRECT:${path}`); }); mock.module("next/navigation", () => ({ redirect: mockRedirect })); type GetSession<T extends UserIdentity> = () => Promise<{ session: AuthSession<T>; failureReason?: SessionFailureReason; }>; const mockConfig: EffectiveAuthConfig<any> = { redirects: { unauthenticated: "/login", unauthorized: "/unauthorized", forbidden: "/forbidden", }, errorMessages: { NotAuthenticatedError: "Auth required", ForbiddenError: "Permission denied", IdentityForbiddenError: "Account suspended", }, baseUrl: "http://localhost:3000", dal: {} as any, secrets: {} as any, cookies: {} as any, jwt: {} as any, debug: false, refreshTokenRotationIntervalSeconds: 0, rateLimit: async () => false, logger: () => {}, providers: {}, csrfEnabled: false, }; const getMockHeaders = (pathname: string) => new Headers({ "x-next-pathname": pathname }); describe("server/protection", () => { beforeEach(() => { mockRedirect.mockClear(); }); describe("protectPage", () => { // This test now also implicitly checks that callbackUrl is added correctly it("should redirect to unauthenticated path with callbackUrl if no session", async () => { const getSession: GetSession<UserIdentity> = async () => ({ session: null, }); const expectedRedirectPath = `${mockConfig.redirects.unauthenticated}?callbackUrl=%2Fdashboard`; await expect( protectPage(getSession, mockConfig, getMockHeaders("/dashboard")) ).rejects.toThrow(`NEXT_REDIRECT:${expectedRedirectPath}`); }); // NEW TEST CASE: Verify custom redirects are used it("should use custom redirect path from options if provided", async () => { const getSession: GetSession<UserIdentity> = async () => ({ session: null, }); const options = { unauthenticatedRedirect: "/custom-login-path", context: {}, }; const expectedRedirectPath = `${options.unauthenticatedRedirect}?callbackUrl=%2Fdashboard`; await expect( protectPage( getSession, mockConfig, getMockHeaders("/dashboard"), options ) ).rejects.toThrow(`NEXT_REDIRECT:${expectedRedirectPath}`); }); }); describe("protectApi", () => { // NEW TEST CASE: Verify success path for protectApi it("should return session object on success", async () => { const getSession: GetSession<UserIdentity> = async () => ({ session: { identity: mockUser }, }); const result = await protectApi(getSession, mockConfig); // Use type assertion to check the successful shape of the return value expect( (result as { session: NonNullable<AuthSession<UserIdentity>> }).session .identity ).toEqual(mockUser); expect((result as { response: NextResponse }).response).toBeUndefined(); }); }); });