fix: require evidence for judgment answers and correct legacy banks
This commit is contained in:
+138
-1
@@ -64,7 +64,7 @@ test('selected, submitContent, userAnswer and top-level answer/answers are not s
|
||||
|
||||
test('judge zero and string zero mean incorrect, never truthy correct', () => {
|
||||
for (const judgeAnswer of [0, '0', 1, '1', null, '-1']) {
|
||||
const [q] = extractQuestions({ listQues: [{ id: 'j1', quesType: 2, content: '压力越高越安全。', judgeAnswer }] });
|
||||
const [q] = extractQuestions({ questionList: [{ id: 'j1', quesType: 2, content: '压力越高越安全。', judgeAnswer }] });
|
||||
const valid = judgeAnswer === 0 || judgeAnswer === '0' || judgeAnswer === 1 || judgeAnswer === '1';
|
||||
assert.equal(q.answerStatus, valid ? 'confirmed' : 'missing');
|
||||
assert.deepEqual(q.answerTexts, valid ? [String(judgeAnswer) === '1' ? '正确' : '错误'] : []);
|
||||
@@ -738,3 +738,140 @@ test('observer ignores unrelated and selection mutations and batches relevant wo
|
||||
assert.ok(reads > baseline, '合批后仍检查真实题目更新');
|
||||
page.adapter.stop();
|
||||
});
|
||||
|
||||
test('unsubmitted listQues judgment placeholders 0 and 1 remain missing without evidence', () => {
|
||||
for (const judgeAnswer of [0, '0', 1, '1']) {
|
||||
for (const submitQuesAnswers of [undefined, []]) {
|
||||
const [record] = extractQuestions({ listQues: [{ id: 'j-unanswered', quesType: 2,
|
||||
content: '未作答题的占位字段不是标准答案', judgeAnswer, submitQuesAnswers, isAnswered: 1 }] });
|
||||
assert.equal(record.answerStatus, 'missing');
|
||||
assert.deepEqual(record.answerTexts, []);
|
||||
assert.deepEqual(record.correctOptionIds, []);
|
||||
assert.equal(record.answerEvidence, undefined);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test('submitted practice judgments use judgeAnswer rather than the student submitted choice', () => {
|
||||
for (const [judgeAnswer, submitted, expected] of [[0, '1', '错误'], [1, '0', '正确'], ['0', '1', '错误'], ['1', '0', '正确']]) {
|
||||
const [record] = extractQuestions({ listQues: [{ id: 'j-answered', quesType: 2,
|
||||
content: '已提交后可用标准反馈', judgeAnswer, submitQuesAnswers: [submitted] }] });
|
||||
assert.equal(record.answerStatus, 'confirmed');
|
||||
assert.deepEqual(record.answerTexts, [expected]);
|
||||
assert.equal(record.answerEvidence, 'submitted-list');
|
||||
}
|
||||
});
|
||||
|
||||
test('an explicit answered resultInfo can confirm a practice judgment independently of placeholders', () => {
|
||||
const [record] = extractQuestions({ listQues: [{ id: 'j-result', quesType: 2, content: '明确答后结果',
|
||||
judgeAnswer: 0, submitQuesAnswers: [], resultInfo: { judgeAnswer: 1 } }] });
|
||||
assert.deepEqual(record.answerTexts, ['正确']);
|
||||
assert.equal(record.answerEvidence, 'submitted-list');
|
||||
const [empty] = extractQuestions({ listQues: [{ id: 'j-empty', quesType: 2, content: '空结果对象也不是标准答案',
|
||||
judgeAnswer: 0, resultInfo: {} }] });
|
||||
assert.equal(empty.answerStatus, 'missing');
|
||||
assert.equal(empty.answerEvidence, undefined);
|
||||
});
|
||||
|
||||
test('explicit questionList standards retain 0/1 semantics while parent hidden flags win', () => {
|
||||
const raw = { id: 'j-standard', quesType: 2, content: '显式题库标准答案', judgeAnswer: 0 };
|
||||
const [standard] = extractQuestions({ questionList: [raw] }, { url: 'https://api.yunxuetang.cn/ote/pra/p1/questions' });
|
||||
assert.deepEqual(standard.answerTexts, ['错误']);
|
||||
assert.equal(standard.answerEvidence, 'explicit-standard');
|
||||
for (const parent of [{ allowViewSingleQuesAnswer: false }, { info: { allowViewSingleQuesAnswer: 0 } }]) {
|
||||
const [hidden] = extractQuestions({ ...parent, data: { questionList: [{ ...raw, allowViewSingleQuesAnswer: true }] } });
|
||||
assert.equal(hidden.answerStatus, 'missing');
|
||||
assert.equal(hidden.answerEvidence, undefined);
|
||||
}
|
||||
});
|
||||
|
||||
test('a direct upm start payload cannot bypass the practice-list evidence gate', async () => {
|
||||
const page = fakePage(async () => new Response(JSON.stringify({ id: 'j-direct', quesType: 2,
|
||||
content: '起始接口直接对象仍需已答证据', judgeAnswer: 1 }), { headers: { 'content-type': 'application/json' } }));
|
||||
page.adapter.start();
|
||||
await page.win.fetch('/upm/wrong/start?praId=p1'); await flush(); await flush();
|
||||
assert.equal(page.received.at(-1).answerStatus, 'missing');
|
||||
assert.equal(page.received.at(-1).answerEvidence, undefined);
|
||||
page.adapter.stop();
|
||||
});
|
||||
|
||||
test('same-question judgment answer receipts confirm standards and anonymize user URL segments', () => {
|
||||
const page = fakePage();
|
||||
page.adapter.start();
|
||||
const initial = new page.win.XMLHttpRequest();
|
||||
initial.open('GET', '/upm/start?praId=p1'); initial.send();
|
||||
initial.respond({ listQues: [{ id: 'j1', quesType: 2, content: '断续噪声的判断示例', judgeAnswer: 0, submitQuesAnswers: [] }] });
|
||||
assert.equal(page.received.at(-1).answerStatus, 'missing');
|
||||
const answer = new page.win.XMLHttpRequest();
|
||||
answer.open('PUT', '/upm/p1/users/private-user/wrong/answer');
|
||||
answer.send(JSON.stringify({ answers: [{ quesId: 'j1', answer: ['0'] }] }));
|
||||
answer.respond([{ id: 'receipt-id', judgeAnswer: 1, corrected: false }]);
|
||||
const record = page.received.at(-1);
|
||||
assert.equal(record.questionId, 'j1');
|
||||
assert.deepEqual(record.answerTexts, ['正确']);
|
||||
assert.equal(record.answerEvidence, 'answer-response');
|
||||
assert.equal(record.source.url, 'https://www.shhuayi-elearning.cn/upm/p1/users/_/wrong/answer');
|
||||
page.adapter.stop();
|
||||
});
|
||||
|
||||
test('DOM standard feedback carries bound or known trusted answer evidence', async () => {
|
||||
const raw = single();
|
||||
raw.choiceItems.forEach(option => delete option.answer);
|
||||
const page = bankPage(async () => new Response(JSON.stringify({ listQues: [raw] }), { headers: { 'content-type': 'application/json' } }));
|
||||
page.adapter.start();
|
||||
await page.win.fetch('/upm/start?praId=bank-a'); await flush(); await flush();
|
||||
page.state.feedback = [{ heading: '正确答案', answer: 'B' }];
|
||||
assert.equal(page.adapter.getCurrentQuestion().answerStatus, 'missing', '未验证网络占位题不能替裸DOM反馈提供证据');
|
||||
page.state.feedback = [{ heading: '正确答案', answer: 'B', questionId: 'q1' }];
|
||||
assert.equal(page.adapter.getCurrentQuestion().answerEvidence, 'bound-dom');
|
||||
const answer = new page.win.XMLHttpRequest();
|
||||
answer.open('PUT', '/upm/bank-a/users/u1/answer');
|
||||
answer.send(JSON.stringify({ answers: [{ quesId: 'q1', answer: ['a1'] }] }));
|
||||
answer.respond([{ answers: ['a2'] }]);
|
||||
page.state.feedback = [{ heading: '正确答案', answer: 'B' }];
|
||||
assert.equal(page.adapter.getCurrentQuestion().answerEvidence, 'answer-response', '与可信同题反馈一致时保留原始证据');
|
||||
page.adapter.stop();
|
||||
});
|
||||
|
||||
test('an older answer receipt cannot attach its answer to a newer same-id full snapshot', () => {
|
||||
const page = fakePage();
|
||||
page.adapter.start();
|
||||
const load = content => {
|
||||
const request = new page.win.XMLHttpRequest();
|
||||
request.open('GET', '/upm/start?praId=p1'); request.send();
|
||||
request.respond({ listQues: [{ id: 'same-id', quesType: 2, content, judgeAnswer: 0, submitQuesAnswers: [] }] });
|
||||
};
|
||||
load('旧版本题干');
|
||||
const answer = new page.win.XMLHttpRequest();
|
||||
answer.open('PUT', '/upm/p1/users/u1/answer');
|
||||
answer.send(JSON.stringify({ answers: [{ quesId: 'same-id', answer: ['0'] }] }));
|
||||
load('重新加载后的新版本题干');
|
||||
const count = page.received.length;
|
||||
answer.respond([{ judgeAnswer: 1 }]);
|
||||
assert.equal(page.received.length, count, '旧回执不能用较新缓存题干拼接成已确认答案');
|
||||
assert.equal(page.received.at(-1).stem, '重新加载后的新版本题干');
|
||||
assert.equal(page.received.at(-1).answerStatus, 'missing');
|
||||
page.adapter.stop();
|
||||
});
|
||||
|
||||
test('a newer snapshot of a different bank does not discard an independent answer receipt', () => {
|
||||
const page = fakePage();
|
||||
page.adapter.start();
|
||||
const first = new page.win.XMLHttpRequest();
|
||||
first.open('GET', '/upm/start?praId=p1'); first.send();
|
||||
first.respond({ listQues: [{ id: 'q1', quesType: 2, content: '第一题库题目', judgeAnswer: 0, submitQuesAnswers: [] }] });
|
||||
const answer = new page.win.XMLHttpRequest();
|
||||
answer.open('PUT', '/upm/p1/users/u1/answer');
|
||||
answer.send(JSON.stringify({ answers: [{ quesId: 'q1', answer: ['0'] }] }));
|
||||
page.doc.title = '第二题库';
|
||||
const second = new page.win.XMLHttpRequest();
|
||||
second.open('GET', '/upm/start?praId=p2'); second.send();
|
||||
second.respond({ listQues: [{ id: 'q1', quesType: 2, content: '第二题库题目', judgeAnswer: 0, submitQuesAnswers: [] }] });
|
||||
answer.respond([{ judgeAnswer: 1 }]);
|
||||
const received = page.received.at(-1);
|
||||
assert.equal(received.bankId, 'p1');
|
||||
assert.equal(received.stem, '第一题库题目');
|
||||
assert.equal(received.answerEvidence, 'answer-response');
|
||||
assert.deepEqual(received.answerTexts, ['正确']);
|
||||
page.adapter.stop();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user