/** * MSW Handlers - 拦截所有 HTTP / GraphQL 请求返回 mock 数据 * * 拦截路径(contract.md §2.3 + §4.2): * - POST /api/auth/login → 返回固定 JWT + UserInfo * - POST /api/v1/teacher/graphql → 按 operationName 返回对应 GraphQL 响应 * * 切换:NEXT_PUBLIC_API_MOCKING=enabled 时启用(见 src/mocks/index.ts) * * 维护者:ai13(teacher-portal) */ import { http, HttpResponse } from "msw"; import { mockUser, mockUserInfo, mockToken } from "./fixtures/user"; import { mockViewports } from "./fixtures/viewports"; import { mockClasses, findClass } from "./fixtures/classes"; import { generateMockStudents } from "./fixtures/students"; import { generateMockExams } from "./fixtures/exams"; import { generateMockHomework } from "./fixtures/homework"; import { generateMockGrades } from "./fixtures/grades"; /** ActionState 信封(coord-final-decisions F9 + 总裁 §3.4 方案 B) */ function envelope(data: T) { return { success: true, errors: null, data }; } /** GraphQL 响应体(urql 期望 { data: ... }) */ function graphqlData(data: T) { return HttpResponse.json({ data }); } /** * 根据 operationName 路由 GraphQL mock 响应 * * 对应 graphql.ts 的 5 个 P2 Query + 4 个 P3 Query + 1 个 Mutation。 */ function handleGraphQL( operationName: string, variables: Record, ) { switch (operationName) { case "Dashboard": return graphqlData({ dashboard: { user: mockUser, classes: mockClasses, viewports: mockViewports, stats: { totalExams: 8, pendingGrading: 12, todayHomework: 3, }, }, }); case "Viewports": return graphqlData({ viewports: mockViewports }); case "Me": return graphqlData({ me: mockUser }); case "Classes": return graphqlData({ classes: mockClasses }); case "Class": { const id = variables.id as string; return graphqlData({ class: findClass(id) }); } case "ClassStudents": { const classId = variables.classId as string; return graphqlData({ classStudents: generateMockStudents(classId) }); } case "ClassExams": { const classId = variables.classId as string; return graphqlData({ classExams: generateMockExams(classId) }); } case "ClassHomework": { const classId = variables.classId as string; return graphqlData({ classHomework: generateMockHomework(classId) }); } case "ExamGrades": { const examId = variables.examId as string; return graphqlData({ examGrades: generateMockGrades(examId) }); } case "UpdateUser": { const input = variables.input as { name?: string; email?: string }; return graphqlData({ updateUser: { ...mockUser, ...input }, }); } default: return HttpResponse.json( { errors: [ { message: `Mock 未覆盖的 GraphQL operation: ${operationName}`, extensions: { code: "BFF_TEACHER_NOT_IMPLEMENTED" }, }, ], }, { status: 200 }, ); } } export const handlers = [ // POST /api/auth/login → 登录 http.post("*/api/auth/login", async ({ request }) => { const body = (await request.json()) as { email?: string; password?: string; }; if (!body.email || !body.password) { return HttpResponse.json( { success: false, error: { code: "GW_UNAUTHORIZED", message: "邮箱和密码必填" }, }, { status: 400 }, ); } return HttpResponse.json( envelope({ user: mockUserInfo, tokens: { accessToken: mockToken, refreshToken: "mock-refresh" }, }), ); }), // POST /api/v1/teacher/graphql → GraphQL endpoint http.post("*/api/v1/teacher/graphql", async ({ request }) => { const body = (await request.json()) as { operationName?: string; query?: string; variables?: Record; }; const operationName = body.operationName ?? extractOperationName(body.query); const variables = body.variables ?? {}; return handleGraphQL(operationName, variables); }), // GET /api/health → liveness http.get("*/api/health", () => HttpResponse.json({ status: "ok", service: "teacher-portal" }), ), // GET /api/ready → readiness http.get("*/api/ready", () => HttpResponse.json({ status: "ok", service: "teacher-portal", checks: { msw: true }, }), ), ]; /** * 从 query 字符串提取 operationName * 兜底:当客户端未传 operationName 时从 query 文本提取 */ function extractOperationName(query?: string): string { if (!query) return "Unknown"; const match = query.match(/(?:query|mutation)\s+(\w+)/); return match?.[1] ?? "Unknown"; }