Phase 6 complete
This commit is contained in:
parent
93ea01525e
commit
3a22b42492
11 changed files with 735 additions and 103 deletions
|
|
@ -223,7 +223,10 @@ async function runTests() {
|
|||
questions: [
|
||||
{
|
||||
text: 'Q?',
|
||||
options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }],
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -356,6 +359,7 @@ async function runTests() {
|
|||
timeLimit: 10,
|
||||
options: [
|
||||
{ text: 'Solo', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'Duo', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
|
@ -383,7 +387,10 @@ async function runTests() {
|
|||
questions: [
|
||||
{
|
||||
text: 'Timestamp Q?',
|
||||
options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }],
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -410,7 +417,10 @@ async function runTests() {
|
|||
questions: [
|
||||
{
|
||||
text: 'Updated Q?',
|
||||
options: [{ text: 'B', isCorrect: true, shape: 'diamond', color: 'blue' }],
|
||||
options: [
|
||||
{ text: 'B', isCorrect: true, shape: 'diamond', color: 'blue' },
|
||||
{ text: 'C', isCorrect: false, shape: 'circle', color: 'yellow' },
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -469,7 +479,10 @@ async function runTests() {
|
|||
questions: [
|
||||
{
|
||||
text: 'Test?',
|
||||
options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }],
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
@ -580,13 +593,13 @@ async function runTests() {
|
|||
const quiz1 = {
|
||||
title: 'Concurrent Quiz 1',
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q1?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
questions: [{ text: 'Q1?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }, { text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' }] }],
|
||||
};
|
||||
const quiz2 = {
|
||||
title: 'Concurrent Quiz 2',
|
||||
source: 'ai_generated',
|
||||
aiTopic: 'Science',
|
||||
questions: [{ text: 'Q2?', options: [{ text: 'B', isCorrect: true, shape: 'diamond', color: 'blue' }] }],
|
||||
questions: [{ text: 'Q2?', options: [{ text: 'C', isCorrect: true, shape: 'circle', color: 'yellow' }, { text: 'D', isCorrect: false, shape: 'square', color: 'green' }] }],
|
||||
};
|
||||
|
||||
const [res1, res2] = await Promise.all([
|
||||
|
|
@ -621,7 +634,7 @@ async function runTests() {
|
|||
const quiz = {
|
||||
title: longTitle,
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }, { text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' }] }],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
|
|
@ -640,7 +653,7 @@ async function runTests() {
|
|||
const quiz = {
|
||||
title: specialTitle,
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }, { text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' }] }],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
|
|
@ -658,12 +671,327 @@ async function runTests() {
|
|||
const quiz = {
|
||||
title: ' ',
|
||||
source: 'manual',
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }] }],
|
||||
questions: [{ text: 'Q?', options: [{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' }, { text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' }] }],
|
||||
};
|
||||
|
||||
await request('POST', '/api/quizzes', quiz, 400);
|
||||
});
|
||||
|
||||
console.log('\nPhase 6 - Error Handling Tests:');
|
||||
|
||||
await test('POST /api/quizzes with question without text returns 400', async () => {
|
||||
const invalidQuiz = {
|
||||
title: 'Quiz with empty question',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: '',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await request('POST', '/api/quizzes', invalidQuiz, 400);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with question with only one option returns 400', async () => {
|
||||
const invalidQuiz = {
|
||||
title: 'Quiz with single option',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Question with one option?',
|
||||
options: [
|
||||
{ text: 'Only one', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await request('POST', '/api/quizzes', invalidQuiz, 400);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with question with no correct answer returns 400', async () => {
|
||||
const invalidQuiz = {
|
||||
title: 'Quiz with no correct answer',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Question with no correct?',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: false, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await request('POST', '/api/quizzes', invalidQuiz, 400);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with invalid source type returns 400', async () => {
|
||||
const invalidQuiz = {
|
||||
title: 'Quiz with invalid source',
|
||||
source: 'invalid_source_type',
|
||||
questions: [
|
||||
{
|
||||
text: 'Question?',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
await request('POST', '/api/quizzes', invalidQuiz, 400);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with null questions returns 400', async () => {
|
||||
const invalidQuiz = {
|
||||
title: 'Quiz with null questions',
|
||||
source: 'manual',
|
||||
questions: null,
|
||||
};
|
||||
await request('POST', '/api/quizzes', invalidQuiz, 400);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with question missing options returns 400', async () => {
|
||||
const invalidQuiz = {
|
||||
title: 'Quiz missing options',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Question without options?',
|
||||
},
|
||||
],
|
||||
};
|
||||
await request('POST', '/api/quizzes', invalidQuiz, 400);
|
||||
});
|
||||
|
||||
await test('PUT /api/quizzes/:id with invalid data returns 400', async () => {
|
||||
const validQuiz = {
|
||||
title: 'Valid Quiz for Update Test',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Valid question?',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', validQuiz, 201);
|
||||
const quizId = (data as { id: string }).id;
|
||||
|
||||
const invalidUpdate = {
|
||||
title: '',
|
||||
questions: [],
|
||||
};
|
||||
|
||||
await request('PUT', `/api/quizzes/${quizId}`, invalidUpdate, 400);
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
console.log('\nPhase 6 - Malformed Request Tests:');
|
||||
|
||||
await test('POST /api/quizzes with malformed JSON returns 400', async () => {
|
||||
const res = await fetch(`${API_URL}/api/quizzes`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${TOKEN}`,
|
||||
},
|
||||
body: '{ invalid json }',
|
||||
});
|
||||
if (res.status !== 400) throw new Error(`Expected 400, got ${res.status}`);
|
||||
});
|
||||
|
||||
await test('GET /api/quizzes/:id with very long ID returns 404', async () => {
|
||||
const longId = 'a'.repeat(1000);
|
||||
await request('GET', `/api/quizzes/${longId}`, undefined, 404);
|
||||
});
|
||||
|
||||
await test('DELETE /api/quizzes/:id with SQL injection attempt returns 404', async () => {
|
||||
const maliciousId = "'; DROP TABLE quizzes; --";
|
||||
await request('DELETE', `/api/quizzes/${encodeURIComponent(maliciousId)}`, undefined, 404);
|
||||
});
|
||||
|
||||
console.log('\nPhase 6 - Content Type Tests:');
|
||||
|
||||
await test('POST /api/quizzes without Content-Type still works with JSON body', async () => {
|
||||
const quiz = {
|
||||
title: 'No Content-Type Quiz',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Question?',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const res = await fetch(`${API_URL}/api/quizzes`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${TOKEN}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(quiz),
|
||||
});
|
||||
|
||||
if (res.status !== 201) throw new Error(`Expected 201, got ${res.status}`);
|
||||
const data = await res.json();
|
||||
await request('DELETE', `/api/quizzes/${data.id}`, undefined, 204);
|
||||
});
|
||||
|
||||
console.log('\nPhase 6 - Boundary Tests:');
|
||||
|
||||
await test('POST /api/quizzes with many questions succeeds', async () => {
|
||||
const manyQuestions = Array.from({ length: 50 }, (_, i) => ({
|
||||
text: `Question ${i + 1}?`,
|
||||
timeLimit: 20,
|
||||
options: [
|
||||
{ text: `A${i}`, isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: `B${i}`, isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
}));
|
||||
|
||||
const quiz = {
|
||||
title: '50 Question Quiz',
|
||||
source: 'manual',
|
||||
questions: manyQuestions,
|
||||
};
|
||||
|
||||
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}`);
|
||||
const savedQuiz = getResult as { questions: unknown[] };
|
||||
if (savedQuiz.questions.length !== 50) {
|
||||
throw new Error(`Expected 50 questions, got ${savedQuiz.questions.length}`);
|
||||
}
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with many options per question succeeds', async () => {
|
||||
const manyOptions = Array.from({ length: 10 }, (_, i) => ({
|
||||
text: `Option ${i + 1}`,
|
||||
isCorrect: i === 0,
|
||||
shape: 'triangle',
|
||||
color: 'red',
|
||||
}));
|
||||
|
||||
const quiz = {
|
||||
title: '10 Options Quiz',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Question with 10 options?',
|
||||
options: manyOptions,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
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}`);
|
||||
const savedQuiz = getResult as { questions: { options: unknown[] }[] };
|
||||
if (savedQuiz.questions[0].options.length !== 10) {
|
||||
throw new Error(`Expected 10 options, got ${savedQuiz.questions[0].options.length}`);
|
||||
}
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
await test('POST /api/quizzes with unicode characters in all fields', async () => {
|
||||
const unicodeQuiz = {
|
||||
title: 'Emoji Quiz title test',
|
||||
source: 'manual',
|
||||
aiTopic: 'Japanese test',
|
||||
questions: [
|
||||
{
|
||||
text: 'What is this character?',
|
||||
timeLimit: 30,
|
||||
options: [
|
||||
{ text: 'Option A', isCorrect: true, shape: 'triangle', color: 'red', reason: 'Because Emoji test!' },
|
||||
{ text: 'Chinese characters', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', unicodeQuiz, 201);
|
||||
const quizId = (data as { id: string }).id;
|
||||
|
||||
const { data: getResult } = await request('GET', `/api/quizzes/${quizId}`);
|
||||
const savedQuiz = getResult as { title: string; questions: { text: string; options: { text: string }[] }[] };
|
||||
|
||||
if (savedQuiz.title !== 'Emoji Quiz title test') throw new Error('Unicode title not preserved');
|
||||
if (savedQuiz.questions[0].text !== 'What is this character?') throw new Error('Unicode question not preserved');
|
||||
if (savedQuiz.questions[0].options[1].text !== 'Chinese characters') throw new Error('Unicode option not preserved');
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
});
|
||||
|
||||
console.log('\nPhase 6 - Duplicate/Idempotency Tests:');
|
||||
|
||||
await test('POST /api/quizzes with same data creates separate quizzes', async () => {
|
||||
const quiz = {
|
||||
title: 'Duplicate Test Quiz',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Same question?',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data: data1 } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
const { data: data2 } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
|
||||
const id1 = (data1 as { id: string }).id;
|
||||
const id2 = (data2 as { id: string }).id;
|
||||
|
||||
if (id1 === id2) throw new Error('Duplicate POST should create separate quizzes with unique IDs');
|
||||
|
||||
await request('DELETE', `/api/quizzes/${id1}`, undefined, 204);
|
||||
await request('DELETE', `/api/quizzes/${id2}`, undefined, 204);
|
||||
});
|
||||
|
||||
await test('DELETE /api/quizzes/:id twice returns 404 on second call', async () => {
|
||||
const quiz = {
|
||||
title: 'Double Delete Quiz',
|
||||
source: 'manual',
|
||||
questions: [
|
||||
{
|
||||
text: 'Q?',
|
||||
options: [
|
||||
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
||||
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const { data } = await request('POST', '/api/quizzes', quiz, 201);
|
||||
const quizId = (data as { id: string }).id;
|
||||
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 204);
|
||||
await request('DELETE', `/api/quizzes/${quizId}`, undefined, 404);
|
||||
});
|
||||
|
||||
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