191 lines
6.4 KiB
TypeScript
191 lines
6.4 KiB
TypeScript
const API_URL = process.env.API_URL || 'http://localhost:3001';
|
|
const TOKEN = process.env.TEST_TOKEN;
|
|
|
|
if (!TOKEN) {
|
|
console.error('ERROR: TEST_TOKEN environment variable is required');
|
|
console.log('Run: npm run test:get-token');
|
|
console.log('Then: export TEST_TOKEN="<token>"');
|
|
process.exit(1);
|
|
}
|
|
|
|
interface TestResult {
|
|
name: string;
|
|
passed: boolean;
|
|
error?: string;
|
|
}
|
|
|
|
const results: TestResult[] = [];
|
|
|
|
async function request(
|
|
method: string,
|
|
path: string,
|
|
body?: unknown,
|
|
expectStatus = 200
|
|
): Promise<{ status: number; data: unknown }> {
|
|
const response = await fetch(`${API_URL}${path}`, {
|
|
method,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${TOKEN}`,
|
|
},
|
|
body: body ? JSON.stringify(body) : undefined,
|
|
});
|
|
|
|
const data = response.headers.get('content-type')?.includes('application/json')
|
|
? await response.json()
|
|
: null;
|
|
|
|
if (response.status !== expectStatus) {
|
|
throw new Error(`Expected ${expectStatus}, got ${response.status}: ${JSON.stringify(data)}`);
|
|
}
|
|
|
|
return { status: response.status, data };
|
|
}
|
|
|
|
async function test(name: string, fn: () => Promise<void>) {
|
|
try {
|
|
await fn();
|
|
results.push({ name, passed: true });
|
|
console.log(` ✓ ${name}`);
|
|
} catch (error) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
results.push({ name, passed: false, error: message });
|
|
console.log(` ✗ ${name}`);
|
|
console.log(` ${message}`);
|
|
}
|
|
}
|
|
|
|
async function runTests() {
|
|
console.log('\n=== Kaboot API Tests ===\n');
|
|
console.log(`API: ${API_URL}`);
|
|
console.log('');
|
|
|
|
let createdQuizId: string | null = null;
|
|
|
|
console.log('Health Check:');
|
|
await test('GET /health returns ok', async () => {
|
|
const res = await fetch(`${API_URL}/health`);
|
|
const data = await res.json();
|
|
if (data.status !== 'ok') throw new Error('Health check failed');
|
|
});
|
|
|
|
console.log('\nAuth Tests:');
|
|
await test('GET /api/quizzes without token returns 401', async () => {
|
|
const res = await fetch(`${API_URL}/api/quizzes`);
|
|
if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`);
|
|
});
|
|
|
|
await test('GET /api/quizzes with invalid token returns 401', async () => {
|
|
const res = await fetch(`${API_URL}/api/quizzes`, {
|
|
headers: { Authorization: 'Bearer invalid-token' },
|
|
});
|
|
if (res.status !== 401) throw new Error(`Expected 401, got ${res.status}`);
|
|
});
|
|
|
|
console.log('\nUser Tests:');
|
|
await test('GET /api/users/me returns user info', async () => {
|
|
const { data } = await request('GET', '/api/users/me');
|
|
const user = data as Record<string, unknown>;
|
|
if (!user.id) throw new Error('Missing user id');
|
|
if (!user.username) throw new Error('Missing username');
|
|
});
|
|
|
|
console.log('\nQuiz CRUD Tests:');
|
|
await test('GET /api/quizzes returns array', async () => {
|
|
const { data } = await request('GET', '/api/quizzes');
|
|
if (!Array.isArray(data)) throw new Error('Expected array');
|
|
});
|
|
|
|
await test('POST /api/quizzes creates quiz', async () => {
|
|
const quiz = {
|
|
title: 'Test Quiz',
|
|
source: 'manual',
|
|
questions: [
|
|
{
|
|
text: 'What is 2 + 2?',
|
|
timeLimit: 20,
|
|
options: [
|
|
{ text: '3', isCorrect: false, shape: 'triangle', color: 'red' },
|
|
{ text: '4', isCorrect: true, shape: 'diamond', color: 'blue' },
|
|
{ text: '5', isCorrect: false, shape: 'circle', color: 'yellow' },
|
|
{ text: '6', isCorrect: false, shape: 'square', color: 'green' },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
const { data } = await request('POST', '/api/quizzes', quiz, 201);
|
|
const result = data as { id: string };
|
|
if (!result.id) throw new Error('Missing quiz id');
|
|
createdQuizId = result.id;
|
|
});
|
|
|
|
await test('GET /api/quizzes/:id returns full quiz', async () => {
|
|
if (!createdQuizId) throw new Error('No quiz created');
|
|
const { data } = await request('GET', `/api/quizzes/${createdQuizId}`);
|
|
const quiz = data as Record<string, unknown>;
|
|
if (quiz.title !== 'Test Quiz') throw new Error('Wrong title');
|
|
if (!Array.isArray(quiz.questions)) throw new Error('Missing questions');
|
|
const questions = quiz.questions as Record<string, unknown>[];
|
|
if (questions.length !== 1) throw new Error('Wrong question count');
|
|
const q = questions[0];
|
|
if (!Array.isArray(q.options)) throw new Error('Missing options');
|
|
if ((q.options as unknown[]).length !== 4) throw new Error('Wrong option count');
|
|
});
|
|
|
|
await test('PUT /api/quizzes/:id updates quiz', async () => {
|
|
if (!createdQuizId) throw new Error('No quiz created');
|
|
const updatedQuiz = {
|
|
title: 'Updated Test Quiz',
|
|
questions: [
|
|
{
|
|
text: 'Updated question?',
|
|
timeLimit: 30,
|
|
options: [
|
|
{ text: 'A', isCorrect: true, shape: 'triangle', color: 'red' },
|
|
{ text: 'B', isCorrect: false, shape: 'diamond', color: 'blue' },
|
|
{ text: 'C', isCorrect: false, shape: 'circle', color: 'yellow' },
|
|
{ text: 'D', isCorrect: false, shape: 'square', color: 'green' },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
|
|
await request('PUT', `/api/quizzes/${createdQuizId}`, updatedQuiz);
|
|
|
|
const { data } = await request('GET', `/api/quizzes/${createdQuizId}`);
|
|
const quiz = data as Record<string, unknown>;
|
|
if (quiz.title !== 'Updated Test Quiz') throw new Error('Title not updated');
|
|
});
|
|
|
|
await test('DELETE /api/quizzes/:id deletes quiz', async () => {
|
|
if (!createdQuizId) throw new Error('No quiz created');
|
|
await request('DELETE', `/api/quizzes/${createdQuizId}`, undefined, 204);
|
|
});
|
|
|
|
await test('GET /api/quizzes/:id returns 404 for deleted quiz', async () => {
|
|
if (!createdQuizId) throw new Error('No quiz created');
|
|
await request('GET', `/api/quizzes/${createdQuizId}`, undefined, 404);
|
|
});
|
|
|
|
console.log('\n=== Results ===');
|
|
const passed = results.filter((r) => r.passed).length;
|
|
const failed = results.filter((r) => !r.passed).length;
|
|
console.log(`Passed: ${passed}/${results.length}`);
|
|
console.log(`Failed: ${failed}/${results.length}`);
|
|
|
|
if (failed > 0) {
|
|
console.log('\nFailed tests:');
|
|
results
|
|
.filter((r) => !r.passed)
|
|
.forEach((r) => console.log(` - ${r.name}: ${r.error}`));
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log('\nAll tests passed!');
|
|
}
|
|
|
|
runTests().catch((err) => {
|
|
console.error('Test runner error:', err);
|
|
process.exit(1);
|
|
});
|