Files
phaseflow/src/middleware.test.ts
Petru Paler 76a46439b3 Implement auth middleware for API routes (P0.2)
Add authentication infrastructure for protected routes:
- withAuth() wrapper for API route handlers (src/lib/auth-middleware.ts)
- Next.js middleware for page protection (src/middleware.ts)

withAuth() loads auth from cookies, validates session, and passes
user context to handlers. Returns 401 for unauthenticated requests.

Page middleware redirects unauthenticated users to /login, while
allowing public routes (/login), API routes (handled separately),
and static assets through.

Tests: 18 new tests (6 for withAuth, 12 for page middleware)
Total test count: 60 tests passing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-10 18:43:19 +00:00

135 lines
4.2 KiB
TypeScript

// ABOUTME: Unit tests for Next.js middleware page protection.
// ABOUTME: Tests that protected routes redirect to login when unauthenticated.
import type { NextRequest } from "next/server";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { config, middleware } from "./middleware";
describe("middleware", () => {
beforeEach(() => {
vi.clearAllMocks();
});
function createMockRequest(url: string, cookieValue?: string): NextRequest {
const request = {
nextUrl: new URL(url, "http://localhost:3000"),
url: `http://localhost:3000${url}`,
cookies: {
get: vi
.fn()
.mockReturnValue(
cookieValue ? { name: "pb_auth", value: cookieValue } : undefined,
),
},
} as unknown as NextRequest;
return request;
}
describe("protected routes", () => {
it("redirects to /login when pb_auth cookie is missing", async () => {
const request = createMockRequest("/");
const response = await middleware(request);
expect(response.status).toBe(307);
expect(response.headers.get("location")).toBe(
"http://localhost:3000/login",
);
});
it("redirects to /login when pb_auth cookie is empty", async () => {
const request = createMockRequest("/", "");
const response = await middleware(request);
expect(response.status).toBe(307);
expect(response.headers.get("location")).toBe(
"http://localhost:3000/login",
);
});
it("allows access when pb_auth cookie is present", async () => {
const request = createMockRequest("/", "valid-token");
const response = await middleware(request);
expect(response.status).toBe(200);
});
it("redirects /settings to /login when unauthenticated", async () => {
const request = createMockRequest("/settings");
const response = await middleware(request);
expect(response.status).toBe(307);
expect(response.headers.get("location")).toBe(
"http://localhost:3000/login",
);
});
it("redirects /calendar to /login when unauthenticated", async () => {
const request = createMockRequest("/calendar");
const response = await middleware(request);
expect(response.status).toBe(307);
expect(response.headers.get("location")).toBe(
"http://localhost:3000/login",
);
});
});
describe("public routes", () => {
it("allows /login without authentication", async () => {
const request = createMockRequest("/login");
const response = await middleware(request);
expect(response.status).toBe(200);
});
it("allows /login even with authentication cookie", async () => {
const request = createMockRequest("/login", "valid-token");
const response = await middleware(request);
// Could redirect authenticated users away from login, but for now just allow
expect(response.status).toBe(200);
});
});
describe("API routes", () => {
it("skips middleware for /api routes (handled by route-level auth)", async () => {
const request = createMockRequest("/api/user");
const response = await middleware(request);
// API routes are not processed by this middleware
expect(response.status).toBe(200);
});
it("skips middleware for /api/calendar ICS endpoint", async () => {
const request = createMockRequest("/api/calendar/user123/token.ics");
const response = await middleware(request);
expect(response.status).toBe(200);
});
});
describe("static assets", () => {
it("skips middleware for _next static assets", async () => {
const request = createMockRequest("/_next/static/chunk.js");
const response = await middleware(request);
expect(response.status).toBe(200);
});
it("skips middleware for favicon", async () => {
const request = createMockRequest("/favicon.ico");
const response = await middleware(request);
expect(response.status).toBe(200);
});
});
});
describe("middleware config", () => {
it("exports a matcher configuration", () => {
expect(config).toBeDefined();
expect(config.matcher).toBeDefined();
expect(Array.isArray(config.matcher)).toBe(true);
});
});