const assert = require("assert"); const fs = require("fs"); const path = require("path"); const root = path.resolve(__dirname, ".."); const api = fs.readFileSync(path.join(root, "src/api/aiTrainingEvaluation.js"), "utf8"); const component = fs.readFileSync(path.join(root, "src/views/components/TrainingAiEvaluation.vue"), "utf8"); const sidebar = fs.readFileSync(path.join(root, "src/views/components/TrainingAiSidebar.vue"), "utf8"); const page = fs.readFileSync(path.join(root, "src/views/training/GenericTrainingPage.vue"), "utf8"); const { normalizeEvaluationReports } = require(path.join(root, "src/views/components/trainingAiEvaluationReport.cjs")); assert(/`\/api\/student\/training-tasks\/\$\{taskKey\}\/ai-evaluation`/.test(api), "evaluation API should use the student task endpoint"); assert(/\/help/.test(api) && /\/assessment/.test(api), "evaluation API should expose help and assessment operations"); assert(/TrainingAiSidebar/.test(page) && /:has-saved-answer="hasSavedAnswer"/.test(page), "generic training page should pass persisted answer state to the AI sidebar"); assert(/form\.value/.test(page) && /workRows\.value/.test(page) && /processRows\.value/.test(page), "hasAnyAnswer should inspect all supported answer shapes"); assert(/requestAiTrainingHelp\(taskKey, question\)/.test(api), "AI help API should accept the student's question"); assert(/data:\s*\{\s*question\s*\}/.test(api), "AI help API should submit the student's question as JSON"); assert(/const question = assistQuestion\.value\.trim\(\)/.test(sidebar), "AI sidebar should trim the student's question before sending it"); assert(/if \(!question\)/.test(sidebar), "AI sidebar should block an empty AI help question"); assert(/requestAiTrainingHelp\(currentTaskKey\.value, question\)/.test(sidebar), "AI sidebar should send the trimmed question to AI help"); assert(/onMounted\(/.test(component) && /watch\(\s*\(\)\s*=>\s*props\.taskKey/.test(component), "AI evaluation should load on mount and task changes"); assert(/AI助学/.test(component) && /AI助评/.test(component), "AI evaluation should offer separate help and assessment controls"); assert(/PROCESSING/.test(component) && /SUCCEEDED/.test(component), "AI evaluation should surface backend statuses"); assert(/!props\.hasAnyAnswer/.test(component), "AI actions should disable when no answer is available"); assert(/proxy\?\.\$modal\?\.confirm/.test(component), "assessment should request confirmation through the modal proxy"); assert(!/v-html/.test(component), "AI reports must be rendered as interpolated text, never v-html"); assert(/scoreCriteria/.test(component), "assessment should display score criteria"); assert(/normalizeEvaluationReports/.test(component), "the component should use the executable report normalizer"); const normalizedFlatPayload = normalizeEvaluationReports({ helpStatus: "succeeded", helpReportJson: JSON.stringify({ summary: "Add supporting data", suggestions: ["Cite the source"] }), assessmentStatus: "succeeded", assessmentScore: 92, assessmentReportJson: JSON.stringify({ summary: "Clear analysis", criteria: [ { criterion: "Evidence", weight: 40, detail: "Uses relevant data" }, "Structure", ], }), }); assert.strictEqual(normalizedFlatPayload.help.status, "SUCCEEDED", "flat help status should be normalized"); assert.match(normalizedFlatPayload.helpReport, /Add supporting data/, "flat help JSON should be formatted for text display"); assert.strictEqual(normalizedFlatPayload.assessment.status, "SUCCEEDED", "flat assessment status should be normalized"); assert.strictEqual(normalizedFlatPayload.score, 92, "assessmentScore should take precedence for the displayed score"); assert.deepStrictEqual(normalizedFlatPayload.scoreCriteria, [ { name: "Evidence", score: 40, description: "Uses relevant data" }, { name: "Structure", score: "", description: "" }, ], "assessment criteria should be mapped from the flat assessment JSON"); const malformedPayload = normalizeEvaluationReports({ helpReportJson: "{not valid JSON", assessmentReportJson: "{also not valid JSON", }); assert.strictEqual(malformedPayload.helpReport, "{not valid JSON", "malformed help JSON should remain safe text"); assert.strictEqual(malformedPayload.assessmentReport, "{also not valid JSON", "malformed assessment JSON should remain safe text"); assert.strictEqual(malformedPayload.score, null, "malformed assessment JSON should not produce a score"); assert.deepStrictEqual(malformedPayload.scoreCriteria, [], "malformed assessment JSON should not produce criteria"); assert(/retry/.test(component) || /重试/.test(component), "failed AI operations should expose a retry action"); assert(/hasSavedAnswer/.test(page) && /:has-saved-answer="hasSavedAnswer"/.test(page), "generic training page should distinguish persisted answers before enabling AI assessment"); assert(/hasSavedAnswer/.test(sidebar), "AI sidebar should accept persisted-answer state"); assert(/!props\.hasSavedAnswer/.test(sidebar), "AI assessment should be disabled until a saved answer exists"); assert(/catch \(error\)/.test(sidebar), "AI sidebar should handle assessment failures without an unhandled rejection");