Files
NextEdu/tests/integration/proxy-guard.test.ts
2026-07-07 12:01:55 +08:00

101 lines
3.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { describe, expect, it, vi } from "vitest"
import { NextRequest } from "next/server"
import { Permissions } from "@/shared/types/permissions"
import { encodePermissionsBitmap } from "@/shared/lib/permission-bitmap"
const { getTokenMock } = vi.hoisted(() => ({
getTokenMock: vi.fn(),
}))
vi.mock("next-auth/jwt", () => ({
getToken: getTokenMock,
}))
import { proxy } from "@/proxy"
const createRequest = (pathname: string) => ({
nextUrl: {
pathname,
clone: () => new URL(`http://localhost${pathname}`),
},
url: `http://localhost${pathname}`,
headers: new Headers(),
})
describe("proxy route guard", () => {
it("redirects unauthenticated requests to login with callback", async () => {
getTokenMock.mockResolvedValue(null)
const response = await proxy(createRequest("/teacher/dashboard") as never)
expect(response.status).toBe(307)
const location = response.headers.get("location") ?? ""
expect(location).toContain("/login")
expect(location).toContain("callbackUrl=")
expect(decodeURIComponent(location)).toContain("/teacher/dashboard")
})
it("redirects user without school:manage permission away from admin routes", async () => {
getTokenMock.mockResolvedValue({
onboarded: true,
roles: ["student"],
permissionsBitmap: encodePermissionsBitmap([Permissions.HOMEWORK_SUBMIT]),
})
const response = await proxy(createRequest("/admin/dashboard") as never)
expect(response.status).toBe(307)
expect(response.headers.get("location")).toContain("/student/dashboard")
})
it("redirects user without grade:manage permission away from management routes", async () => {
getTokenMock.mockResolvedValue({
onboarded: true,
roles: ["parent"],
permissionsBitmap: encodePermissionsBitmap([Permissions.DASHBOARD_PARENT_READ]),
})
const response = await proxy(createRequest("/management/grade/insights") as never)
expect(response.status).toBe(307)
expect(response.headers.get("location")).toContain("/parent/dashboard")
})
it("allows user with grade:manage permission to access management routes", async () => {
getTokenMock.mockResolvedValue({
onboarded: true,
roles: ["teacher"],
permissionsBitmap: encodePermissionsBitmap([
Permissions.GRADE_MANAGE,
Permissions.EXAM_CREATE,
]),
})
const response = await proxy(createRequest("/management/grade/insights") as never)
expect(response.status).toBe(200)
expect(response.headers.get("location")).toBeNull()
})
})
describe("proxy requestId 注入", () => {
it("未带 x-request-id 请求头时response 头包含新生成的 x-request-id", async () => {
getTokenMock.mockResolvedValue({
onboarded: true,
roles: ["teacher"],
permissionsBitmap: encodePermissionsBitmap([Permissions.EXAM_CREATE]),
})
const req = new NextRequest(new URL("/dashboard", "http://localhost:3000"))
const res = await proxy(req)
expect(res.headers.get("x-request-id")).toBeDefined()
expect(res.headers.get("x-request-id")).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i)
})
it("带 x-request-id 请求头时response 头沿用请求头中的 id", async () => {
const existingId = "client-supplied-id"
getTokenMock.mockResolvedValue({
onboarded: true,
roles: ["teacher"],
permissionsBitmap: encodePermissionsBitmap([Permissions.EXAM_CREATE]),
})
const req = new NextRequest(new URL("/dashboard", "http://localhost:3000"), {
headers: { "x-request-id": existingId },
})
const res = await proxy(req)
expect(res.headers.get("x-request-id")).toBe(existingId)
})
})