diff --git a/server/tests/api.test.ts b/server/tests/api.test.ts index 268f68f..1cdb382 100644 --- a/server/tests/api.test.ts +++ b/server/tests/api.test.ts @@ -1994,6 +1994,171 @@ console.log('\n=== Game Session Tests ==='); }); }); + console.log('\n=== AI Generate Endpoint Tests ==='); + + console.log('\nGenerate Status Tests:'); + + await test('GET /api/generate/status returns availability status', async () => { + const res = await fetch(`${API_URL}/api/generate/status`); + if (res.status !== 200) throw new Error(`Expected 200, got ${res.status}`); + + const data = await res.json(); + if (typeof data.available !== 'boolean') throw new Error('Missing or invalid available field'); + if (typeof data.model !== 'string') throw new Error('Missing or invalid model field'); + }); + + console.log('\nGenerate Auth Tests:'); + + await test('POST /api/generate without token returns 401', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ topic: 'Test topic' }), + }); + if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`); + }); + + await test('POST /api/generate with invalid token returns 401', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: 'Bearer invalid-token-12345', + }, + body: JSON.stringify({ topic: 'Test topic' }), + }); + if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`); + }); + + console.log('\nGenerate Validation Tests:'); + + await test('POST /api/generate without topic or documents returns error', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, + }, + body: JSON.stringify({}), + }); + + const VALID_STATUSES = [400, 403, 503]; + if (!VALID_STATUSES.includes(res.status)) { + throw new Error(`Expected one of ${VALID_STATUSES.join('/')}, got ${res.status}`); + } + }); + + await test('POST /api/generate with empty topic and no documents returns error', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, + }, + body: JSON.stringify({ topic: '', documents: [] }), + }); + + const VALID_STATUSES = [400, 403, 503]; + if (!VALID_STATUSES.includes(res.status)) { + throw new Error(`Expected one of ${VALID_STATUSES.join('/')}, got ${res.status}`); + } + }); + + await test('POST /api/generate with only questionCount returns error', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, + }, + body: JSON.stringify({ questionCount: 5 }), + }); + + const VALID_STATUSES = [400, 403, 503]; + if (!VALID_STATUSES.includes(res.status)) { + throw new Error(`Expected one of ${VALID_STATUSES.join('/')}, got ${res.status}`); + } + }); + + console.log('\nGenerate Access Control Tests:'); + + await test('POST /api/generate with valid auth checks group access', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, + }, + body: JSON.stringify({ topic: 'Test topic' }), + }); + + const VALID_STATUSES = [200, 403, 503]; + if (!VALID_STATUSES.includes(res.status)) { + throw new Error(`Expected one of ${VALID_STATUSES.join('/')}, got ${res.status}`); + } + }); + + console.log('\nGenerate with Topic Tests:'); + + await test('POST /api/generate with valid topic returns quiz or expected error', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, + }, + body: JSON.stringify({ topic: 'World History', questionCount: 3 }), + }); + + const data = await res.json(); + + if (res.status === 403) { + if (!data.error) throw new Error('403 response missing error message'); + } else if (res.status === 503) { + if (!data.error) throw new Error('503 response missing error message'); + } else if (res.status === 200) { + if (!data.title) throw new Error('Missing quiz title'); + if (!Array.isArray(data.questions)) throw new Error('Missing questions array'); + if (data.questions.length === 0) throw new Error('Empty questions array'); + + const q = data.questions[0]; + if (!q.id) throw new Error('Missing question id'); + if (!q.text) throw new Error('Missing question text'); + if (!Array.isArray(q.options)) throw new Error('Missing options array'); + if (q.options.length !== 4) throw new Error('Expected 4 options per question'); + + const opt = q.options[0]; + if (!opt.text) throw new Error('Missing option text'); + if (typeof opt.isCorrect !== 'boolean') throw new Error('Missing option isCorrect'); + if (!opt.shape) throw new Error('Missing option shape'); + if (!opt.color) throw new Error('Missing option color'); + } else { + throw new Error(`Unexpected status ${res.status}: ${JSON.stringify(data)}`); + } + }); + + await test('POST /api/generate with documents validates structure', async () => { + const res = await fetch(`${API_URL}/api/generate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${TOKEN}`, + }, + body: JSON.stringify({ + topic: 'Document content', + documents: [ + { type: 'text', content: 'This is test content about programming.' } + ], + questionCount: 2 + }), + }); + + const VALID_STATUSES = [200, 403, 503]; + if (!VALID_STATUSES.includes(res.status)) { + throw new Error(`Expected one of ${VALID_STATUSES.join('/')}, got ${res.status}`); + } + }); + console.log('\n=== Results ==='); const passed = results.filter((r) => r.passed).length; const failed = results.filter((r) => !r.passed).length;