import { describe, it, expect } from "vitest"; import { mapParent, mapChild, mapClassInfo, mapGrade, mapHomework, mapExam, mapViewport, mapWeakness, mapTrend, mapAnalytics, } from "../../src/aggregation/response-mapper.js"; import type { UserInfoDto, ChildDto, GradeDto, HomeworkDto, ExamDto, ViewportDto, StudentWeaknessDto, LearningTrendDto, ClassPerformanceDto, } from "../../src/clients/dtos.js"; describe("Response Mapper", () => { describe("mapParent", () => { it("正确映射 UserInfoDto → ParentType", () => { const dto: UserInfoDto = { id: "parent-001", email: "parent@example.com", name: "王家长", roles: ["parent"], permissions: [], }; const result = mapParent(dto); expect(result.id).toBe("parent-001"); expect(result.email).toBe("parent@example.com"); expect(result.name).toBe("王家长"); expect(result.avatar).toBeNull(); expect(result.roles).toEqual(["parent"]); expect(result.dataScope).toBe("CHILDREN"); }); }); describe("mapChild", () => { it("正确映射 ChildDto → ChildType", () => { const dto: ChildDto = { id: "student-001", name: "李同学", grade: "三年级", classId: "class-001", className: "三年级1班", gradeId: "grade-003", }; const result = mapChild(dto); expect(result.id).toBe("student-001"); expect(result.name).toBe("李同学"); expect(result.grade).toBe("三年级"); expect(result.class.id).toBe("class-001"); expect(result.class.name).toBe("三年级1班"); expect(result.class.gradeId).toBe("grade-003"); }); }); describe("mapClassInfo", () => { it("正确映射 ClassInfo", () => { const result = mapClassInfo({ id: "class-001", name: "三年级1班", gradeId: "grade-003", }); expect(result.id).toBe("class-001"); expect(result.name).toBe("三年级1班"); expect(result.gradeId).toBe("grade-003"); }); }); describe("mapGrade", () => { it("正确映射 GradeDto → GradeType(score string → number)", () => { const dto: GradeDto = { id: "grade-001", studentId: "student-001", examId: "exam-001", homeworkId: "", score: "85.5", feedback: "数学表现良好", gradedBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-02T00:00:00.000Z", }; const result = mapGrade(dto); expect(result.id).toBe("grade-001"); expect(result.examId).toBe("exam-001"); expect(result.score).toBe(85.5); expect(result.subject).toBe("数学"); expect(result.examTitle).toBe("数学表现良好"); expect(result.rank).toBeNull(); expect(result.gradedAt).toBe("2026-01-02T00:00:00.000Z"); }); it("feedback 为空时 examTitle 为默认值", () => { const dto: GradeDto = { id: "grade-001", studentId: "student-001", examId: "exam-001", homeworkId: "", score: "90", feedback: "", gradedBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-02T00:00:00.000Z", }; const result = mapGrade(dto); expect(result.examTitle).toBe("未命名考试"); expect(result.subject).toBe("未知科目"); }); it("score 非数字时返回 0", () => { const dto: GradeDto = { id: "grade-001", studentId: "student-001", examId: "exam-001", homeworkId: "", score: "invalid", feedback: "语文表现良好", gradedBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-02T00:00:00.000Z", }; const result = mapGrade(dto); expect(result.score).toBe(0); expect(result.subject).toBe("语文"); }); it("feedback 无科目前缀时 subject 为综合", () => { const dto: GradeDto = { id: "grade-001", studentId: "student-001", examId: "exam-001", homeworkId: "", score: "80", feedback: "表现不错", gradedBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-02T00:00:00.000Z", }; const result = mapGrade(dto); expect(result.subject).toBe("综合"); }); }); describe("mapHomework", () => { it("SUBMITTED 状态返回 submittedAt", () => { const dto: HomeworkDto = { id: "hw-001", classId: "class-001", title: "数学练习", description: "", dueDate: "2026-01-05T00:00:00.000Z", status: "SUBMITTED", createdBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-02T00:00:00.000Z", }; const result = mapHomework(dto); expect(result.status).toBe("SUBMITTED"); expect(result.submittedAt).toBe("2026-01-02T00:00:00.000Z"); }); it("GRADED 状态返回 submittedAt", () => { const dto: HomeworkDto = { id: "hw-001", classId: "class-001", title: "数学练习", description: "", dueDate: "2026-01-05T00:00:00.000Z", status: "GRADED", createdBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-03T00:00:00.000Z", }; const result = mapHomework(dto); expect(result.submittedAt).toBe("2026-01-03T00:00:00.000Z"); }); it("NOT_SUBMITTED 状态 submittedAt 为 null", () => { const dto: HomeworkDto = { id: "hw-001", classId: "class-001", title: "数学练习", description: "", dueDate: "2026-01-05T00:00:00.000Z", status: "NOT_SUBMITTED", createdBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-02T00:00:00.000Z", }; const result = mapHomework(dto); expect(result.submittedAt).toBeNull(); }); }); describe("mapExam", () => { it("PUBLISHED 状态返回 publishedAt", () => { const dto: ExamDto = { id: "exam-001", classId: "class-001", title: "数学期中考试", description: "", examDate: "2026-02-01T00:00:00.000Z", duration: "90", totalScore: "100", status: "PUBLISHED", createdBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-15T00:00:00.000Z", }; const result = mapExam(dto); expect(result.status).toBe("PUBLISHED"); expect(result.publishedAt).toBe("2026-01-15T00:00:00.000Z"); }); it("DRAFT 状态 publishedAt 为 null", () => { const dto: ExamDto = { id: "exam-001", classId: "class-001", title: "数学期中考试", description: "", examDate: "2026-02-01T00:00:00.000Z", duration: "90", totalScore: "100", status: "DRAFT", createdBy: "teacher-001", createdAt: "2026-01-01T00:00:00.000Z", updatedAt: "2026-01-15T00:00:00.000Z", }; const result = mapExam(dto); expect(result.publishedAt).toBeNull(); }); }); describe("mapViewport", () => { it("正确映射 ViewportDto", () => { const dto: ViewportDto = { key: "dashboard", label: "首页", route: "/parent/dashboard", icon: "home", sortOrder: "1", requiredPermission: "parent:dashboard:view", }; const result = mapViewport(dto); expect(result.key).toBe("dashboard"); expect(result.label).toBe("首页"); expect(result.icon).toBe("home"); expect(result.requiredPermission).toBe("parent:dashboard:view"); }); it("icon 和 requiredPermission 可选", () => { const dto: ViewportDto = { key: "settings", label: "设置", route: "/parent/settings", sortOrder: "5", }; const result = mapViewport(dto); expect(result.icon).toBeNull(); expect(result.requiredPermission).toBeNull(); }); }); describe("mapWeakness", () => { it("正确映射薄弱点列表", () => { const dto: StudentWeaknessDto = { studentId: "student-001", weakPoints: [ { knowledgePointId: "kp-1", title: "分数加减法", mastery: 0.45 }, { knowledgePointId: "kp-2", title: "阅读理解", mastery: 0.62 }, ], }; const result = mapWeakness(dto); expect(result).toHaveLength(2); expect(result[0]!.knowledgePointId).toBe("kp-1"); expect(result[0]!.name).toBe("分数加减法"); expect(result[0]!.masteryRate).toBe(0.45); expect(result[0]!.subject).toBe("综合"); }); it("空薄弱点列表", () => { const dto: StudentWeaknessDto = { studentId: "student-001", weakPoints: [], }; const result = mapWeakness(dto); expect(result).toHaveLength(0); }); }); describe("mapTrend", () => { it("正确映射趋势点(int64 ms → ISO string)", () => { const dto: LearningTrendDto = { studentId: "student-001", points: [ { date: 1735689600000, score: 80 }, { date: 1735776000000, score: 85 }, ], }; const result = mapTrend(dto); expect(result).toHaveLength(2); expect(result[0]!.score).toBe(80); expect(typeof result[0]!.date).toBe("string"); expect(new Date(result[0]!.date).getTime()).toBe(1735689600000); expect(result[0]!.subject).toBeNull(); }); }); describe("mapAnalytics", () => { it("正确计算 classRank(降序排名)", () => { const weakness: StudentWeaknessDto = { studentId: "student-001", weakPoints: [], }; const trend: LearningTrendDto = { studentId: "student-001", points: [], }; const classPerf: ClassPerformanceDto = { classId: "class-001", averageScore: 82.5, passRate: 0.9, scores: [ { studentId: "student-001", score: 85, grade: "A" }, { studentId: "student-002", score: 92, grade: "A" }, { studentId: "student-003", score: 65, grade: "C" }, ], }; const result = mapAnalytics("student-001", weakness, trend, classPerf); expect(result.childId).toBe("student-001"); expect(result.classAverage).toBe(82.5); const sorted = [...classPerf.scores].sort((a, b) => b.score - a.score); const idx = sorted.findIndex((s) => s.studentId === "student-001"); expect(result.classRank).toBe(idx + 1); }); it("classPerf 为 null 时 classRank 和 classAverage 为 null", () => { const weakness: StudentWeaknessDto = { studentId: "student-001", weakPoints: [], }; const trend: LearningTrendDto = { studentId: "student-001", points: [], }; const result = mapAnalytics("student-001", weakness, trend, null); expect(result.classRank).toBeNull(); expect(result.classAverage).toBeNull(); }); it("childId 不在 scores 中时 classRank 为 null", () => { const weakness: StudentWeaknessDto = { studentId: "student-001", weakPoints: [], }; const trend: LearningTrendDto = { studentId: "student-001", points: [], }; const classPerf: ClassPerformanceDto = { classId: "class-001", averageScore: 80, passRate: 0.9, scores: [ { studentId: "student-002", score: 90, grade: "A" }, { studentId: "student-003", score: 70, grade: "B" }, ], }; const result = mapAnalytics("student-001", weakness, trend, classPerf); expect(result.classRank).toBeNull(); expect(result.classAverage).toBe(80); }); it("scores 为空数组时 classRank 为 null", () => { const weakness: StudentWeaknessDto = { studentId: "student-001", weakPoints: [], }; const trend: LearningTrendDto = { studentId: "student-001", points: [], }; const classPerf: ClassPerformanceDto = { classId: "class-001", averageScore: 0, passRate: 0, scores: [], }; const result = mapAnalytics("student-001", weakness, trend, classPerf); expect(result.classRank).toBeNull(); }); }); });