Phase 5 complete
This commit is contained in:
parent
342ff60b70
commit
93ea01525e
7 changed files with 433 additions and 37 deletions
|
|
@ -426,6 +426,244 @@ async function runTests() {
|
|||
await request('DELETE', `/api/quizzes/${timestampQuizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
console.log('\nSave Integration Tests (Phase 5):');
|
||||
let manualSaveQuizId: string | null = null;
|
||||
let aiSaveQuizId: string | null = null;
|
||||
|
||||
await test('POST /api/quizzes manual quiz without aiTopic', async () => {
|
||||
const manualQuiz = {
|
||||
title: 'Manual Save Test Quiz',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'What is 1+1?',
|
||||
timeLimit: 20,
|
||||
options: [
|
||||
{ text: '1', isCorrect: false, shape: 'triangle', color: 'red' },
|
||||
{ text: '2', isCorrect: true, shape: 'diamond', color: 'blue' },
|
||||
{ text: '3', isCorrect: false, shape: 'circle', color: 'yellow' },
|
||||
{ text: '4', isCorrect: false, shape: 'square', color: 'green' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', manualQuiz, 201);
|
||||
const result = data as { id: string };
|
||||
manualSaveQuizId = result.id;
|
||||
});
|
||||
|
||||
await test('GET manual quiz has null aiTopic', async () => {
|
||||
if (!manualSaveQuizId) throw new Error('No manual quiz created');
|
||||
const { data } = await request('GET', `/api/quizzes/${manualSaveQuizId}`);
|
||||
const quiz = data as Record<string, unknown>;
|
||||
if (quiz.source !== 'manual') throw new Error('Wrong source');
|
||||
if (quiz.aiTopic !== null) throw new Error(`Expected null aiTopic, got ${quiz.aiTopic}`);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes ai_generated with empty aiTopic treated as null', async () => {
|
||||
const aiQuiz = {
|
||||
title: 'AI Quiz Empty Topic',
|
||||
source: 'ai_generated',
|
||||
aiTopic: '',
|
||||
questions: [
|
||||
{
|
||||
text: 'Test?',
|
||||
options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', aiQuiz, 201);
|
||||
aiSaveQuizId = (data as { id: string }).id;
|
||||
|
||||
const { data: getResult } = await request('GET', `/api/quizzes/${aiSaveQuizId}`);
|
||||
const quiz = getResult as Record<string, unknown>;
|
||||
if (quiz.aiTopic !== null && quiz.aiTopic !== '') {
|
||||
throw new Error(`Expected null/empty aiTopic for empty string, got ${quiz.aiTopic}`);
|
||||
}
|
||||
});
|
||||
|
||||
await test('DELETE cleanup manual save quiz', async () => {
|
||||
if (manualSaveQuizId) {
|
||||
await request('DELETE', `/api/quizzes/${manualSaveQuizId}`, undefined, 204);
|
||||
}
|
||||
});
|
||||
|
||||
await test('DELETE cleanup ai save quiz', async () => {
|
||||
if (aiSaveQuizId) {
|
||||
await request('DELETE', `/api/quizzes/${aiSaveQuizId}`, undefined, 204);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\nOption Preservation Tests:');
|
||||
let optionQuizId: string | null = null;
|
||||
|
||||
await test('POST /api/quizzes preserves all option fields including reason', async () => {
|
||||
const quizWithReasons = {
|
||||
title: 'Quiz With Reasons',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Capital of France?',
|
||||
timeLimit: 15,
|
||||
options: [
|
||||
{ text: 'London', isCorrect: false, shape: 'triangle', color: 'red', reason: 'London is the capital of UK' },
|
||||
{ text: 'Paris', isCorrect: true, shape: 'diamond', color: 'blue', reason: 'Correct! Paris is the capital of France' },
|
||||
{ text: 'Berlin', isCorrect: false, shape: 'circle', color: 'yellow', reason: 'Berlin is the capital of Germany' },
|
||||
{ text: 'Madrid', isCorrect: false, shape: 'square', color: 'green', reason: 'Madrid is the capital of Spain' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quizWithReasons, 201);
|
||||
optionQuizId = (data as { id: string }).id;
|
||||
|
||||
const { data: getResult } = await request('GET', `/api/quizzes/${optionQuizId}`);
|
||||
const quiz = getResult as { questions: { options: { text: string; isCorrect: boolean; shape: string; color: string; reason?: string }[] }[] };
|
||||
|
||||
const options = quiz.questions[0].options;
|
||||
if (options.length !== 4) throw new Error('Expected 4 options');
|
||||
|
||||
const parisOpt = options.find(o => o.text === 'Paris');
|
||||
if (!parisOpt) throw new Error('Paris option not found');
|
||||
if (!parisOpt.isCorrect) throw new Error('Paris should be correct');
|
||||
if (parisOpt.reason !== 'Correct! Paris is the capital of France') throw new Error('Paris reason not preserved');
|
||||
|
||||
const londonOpt = options.find(o => o.text === 'London');
|
||||
if (!londonOpt) throw new Error('London option not found');
|
||||
if (londonOpt.isCorrect) throw new Error('London should not be correct');
|
||||
if (londonOpt.reason !== 'London is the capital of UK') throw new Error('London reason not preserved');
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes options without reason field are preserved', async () => {
|
||||
const quizNoReasons = {
|
||||
title: 'Quiz Without Reasons',
|
||||
source: 'ai_generated',
|
||||
aiTopic: 'Geography',
|
||||
questions: [
|
||||
{
|
||||
text: 'Largest ocean?',
|
||||
options: [
|
||||
{ text: 'Atlantic', isCorrect: false, shape: 'triangle', color: 'red' },
|
||||
{ text: 'Pacific', isCorrect: true, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quizNoReasons, 201);
|
||||
const quizId = (data as { id: string }).id;
|
||||
|
||||
const { data: getResult } = await request('GET', `/api/quizzes/${quizId}`);
|
||||
const quiz = getResult as { questions: { options: { reason?: string }[] }[] };
|
||||
|
||||
const options = quiz.questions[0].options;
|
||||
const pacificOpt = options.find((o: any) => o.text === 'Pacific');
|
||||
if (pacificOpt?.reason !== null && pacificOpt?.reason !== undefined) {
|
||||
throw new Error('Expected null/undefined reason for option without reason');
|
||||
}
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
await test('DELETE cleanup option quiz', async () => {
|
||||
if (optionQuizId) {
|
||||
await request('DELETE', `/api/quizzes/${optionQuizId}`, undefined, 204);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('\nConcurrent Save Tests:');
|
||||
|
||||
await test('Multiple quizzes can be saved by same user', async () => {
|
||||
const quiz1 = {
|
||||
title: 'Concurrent Quiz 1',
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q1?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
};
|
||||
const quiz2 = {
|
||||
title: 'Concurrent Quiz 2',
|
||||
source: 'ai_generated',
|
||||
aiTopic: 'Science',
|
||||
questions: [{ text: 'Q2?', options: [{ text: 'B', isCorrect: true, shape: 'diamond', color: 'blue' }] }],
|
||||
};
|
||||
|
||||
const [res1, res2] = await Promise.all([
|
||||
request('POST', '/api/quizzes', quiz1, 201),
|
||||
request('POST', '/api/quizzes', quiz2, 201),
|
||||
]);
|
||||
|
||||
const id1 = (res1.data as { id: string }).id;
|
||||
const id2 = (res2.data as { id: string }).id;
|
||||
|
||||
if (id1 === id2) throw new Error('Quiz IDs should be unique');
|
||||
|
||||
const { data: listData } = await request('GET', '/api/quizzes');
|
||||
const list = listData as { id: string; title: string }[];
|
||||
|
||||
const found1 = list.find(q => q.id === id1);
|
||||
const found2 = list.find(q => q.id === id2);
|
||||
|
||||
if (!found1) throw new Error('Quiz 1 not in list');
|
||||
if (!found2) throw new Error('Quiz 2 not in list');
|
||||
|
||||
await Promise.all([
|
||||
request('DELETE', `/api/quizzes/${id1}`, undefined, 204),
|
||||
request('DELETE', `/api/quizzes/${id2}`, undefined, 204),
|
||||
]);
|
||||
});
|
||||
|
||||
console.log('\nEdge Case Tests:');
|
||||
|
||||
await test('POST /api/quizzes with very long title', async () => {
|
||||
const longTitle = 'A'.repeat(500);
|
||||
const quiz = {
|
||||
title: longTitle,
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
const quizId = (data as { id: string }).id;
|
||||
|
||||
const { data: getResult } = await request('GET', `/api/quizzes/${quizId}`);
|
||||
if ((getResult as { title: string }).title !== longTitle) {
|
||||
throw new Error('Long title not preserved');
|
||||
}
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with special characters in title', async () => {
|
||||
const specialTitle = 'Quiz with "quotes" & <tags> and emoji test';
|
||||
const quiz = {
|
||||
title: specialTitle,
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
const quizId = (data as { id: string }).id;
|
||||
|
||||
const { data: getResult } = await request('GET', `/api/quizzes/${quizId}`);
|
||||
if ((getResult as { title: string }).title !== specialTitle) {
|
||||
throw new Error('Special characters not preserved');
|
||||
}
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with whitespace-only title returns 400', async () => {
|
||||
const quiz = {
|
||||
title: ' ',
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
};
|
||||
|
||||
await request('POST', '/api/quizzes', quiz, 400);
|
||||
});
|
||||
|
||||
console.log('\n=== Results ===');
|
||||
const passed = results.filter((r) => r.passed).length;
|
||||
const failed = results.filter((r) => !r.passed).length;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue