412 lines
14 KiB
JavaScript
412 lines
14 KiB
JavaScript
const axios = require('axios');
|
|
|
|
const API_URL = 'http://localhost:3000/api';
|
|
|
|
// Test users
|
|
const testUser = {
|
|
username: 'bookmarktest',
|
|
email: 'bookmarktest@example.com',
|
|
password: 'Test123!@#'
|
|
};
|
|
|
|
const secondUser = {
|
|
username: 'bookmarktest2',
|
|
email: 'bookmarktest2@example.com',
|
|
password: 'Test123!@#'
|
|
};
|
|
|
|
let userToken;
|
|
let userId;
|
|
let secondUserToken;
|
|
let secondUserId;
|
|
let questionId; // Will get from a real question
|
|
let categoryId; // Will get from a real category
|
|
|
|
// Test setup
|
|
async function setup() {
|
|
console.log('Setting up test data...\n');
|
|
|
|
try {
|
|
// Register/login user
|
|
try {
|
|
const registerRes = await axios.post(`${API_URL}/auth/register`, testUser);
|
|
userToken = registerRes.data.data.token;
|
|
userId = registerRes.data.data.user.id;
|
|
console.log('✓ Test user registered');
|
|
} catch (error) {
|
|
const loginRes = await axios.post(`${API_URL}/auth/login`, {
|
|
email: testUser.email,
|
|
password: testUser.password
|
|
});
|
|
userToken = loginRes.data.data.token;
|
|
userId = loginRes.data.data.user.id;
|
|
console.log('✓ Test user logged in');
|
|
}
|
|
|
|
// Register/login second user
|
|
try {
|
|
const registerRes = await axios.post(`${API_URL}/auth/register`, secondUser);
|
|
secondUserToken = registerRes.data.data.token;
|
|
secondUserId = registerRes.data.data.user.id;
|
|
console.log('✓ Second user registered');
|
|
} catch (error) {
|
|
const loginRes = await axios.post(`${API_URL}/auth/login`, {
|
|
email: secondUser.email,
|
|
password: secondUser.password
|
|
});
|
|
secondUserToken = loginRes.data.data.token;
|
|
secondUserId = loginRes.data.data.user.id;
|
|
console.log('✓ Second user logged in');
|
|
}
|
|
|
|
// Get a real category with questions
|
|
const categoriesRes = await axios.get(`${API_URL}/categories`, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
if (!categoriesRes.data.data || categoriesRes.data.data.length === 0) {
|
|
throw new Error('No categories available for testing');
|
|
}
|
|
|
|
// Find a category that has questions
|
|
let foundQuestion = false;
|
|
for (const category of categoriesRes.data.data) {
|
|
if (category.questionCount > 0) {
|
|
categoryId = category.id;
|
|
console.log(`✓ Found test category: ${category.name} (${category.questionCount} questions)`);
|
|
|
|
// Get a real question from that category
|
|
const questionsRes = await axios.get(`${API_URL}/questions/category/${categoryId}?limit=1`, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
if (questionsRes.data.data.length > 0) {
|
|
questionId = questionsRes.data.data[0].id;
|
|
console.log(`✓ Found test question`);
|
|
foundQuestion = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!foundQuestion) {
|
|
throw new Error('No questions available for testing. Please seed the database first.');
|
|
}
|
|
|
|
console.log('');
|
|
} catch (error) {
|
|
console.error('Setup failed:', error.response?.data || error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Tests
|
|
const tests = [
|
|
{
|
|
name: 'Test 1: Add bookmark successfully',
|
|
run: async () => {
|
|
const response = await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
|
|
questionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
|
|
if (!response.data.success) throw new Error('Request failed');
|
|
if (response.status !== 201) throw new Error(`Expected 201, got ${response.status}`);
|
|
if (!response.data.data.id) throw new Error('Missing bookmark ID');
|
|
if (response.data.data.questionId !== questionId) {
|
|
throw new Error('Question ID mismatch');
|
|
}
|
|
if (!response.data.data.bookmarkedAt) throw new Error('Missing bookmarkedAt timestamp');
|
|
if (!response.data.data.question) throw new Error('Missing question details');
|
|
|
|
return '✓ Bookmark added successfully';
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 2: Reject duplicate bookmark',
|
|
run: async () => {
|
|
try {
|
|
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
|
|
questionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 409) {
|
|
throw new Error(`Expected 409, got ${error.response?.status}`);
|
|
}
|
|
if (!error.response.data.message.toLowerCase().includes('already')) {
|
|
throw new Error('Error message should mention already bookmarked');
|
|
}
|
|
return '✓ Duplicate bookmark rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 3: Remove bookmark successfully',
|
|
run: async () => {
|
|
const response = await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
|
|
if (!response.data.success) throw new Error('Request failed');
|
|
if (response.status !== 200) throw new Error(`Expected 200, got ${response.status}`);
|
|
if (response.data.data.questionId !== questionId) {
|
|
throw new Error('Question ID mismatch');
|
|
}
|
|
|
|
return '✓ Bookmark removed successfully';
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 4: Reject removing non-existent bookmark',
|
|
run: async () => {
|
|
try {
|
|
await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 404) {
|
|
throw new Error(`Expected 404, got ${error.response?.status}`);
|
|
}
|
|
if (!error.response.data.message.toLowerCase().includes('not found')) {
|
|
throw new Error('Error message should mention not found');
|
|
}
|
|
return '✓ Non-existent bookmark rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 5: Reject missing questionId',
|
|
run: async () => {
|
|
try {
|
|
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 400) {
|
|
throw new Error(`Expected 400, got ${error.response?.status}`);
|
|
}
|
|
if (!error.response.data.message.toLowerCase().includes('required')) {
|
|
throw new Error('Error message should mention required');
|
|
}
|
|
return '✓ Missing questionId rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 6: Reject invalid questionId format',
|
|
run: async () => {
|
|
try {
|
|
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
|
|
questionId: 'invalid-uuid'
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 400) {
|
|
throw new Error(`Expected 400, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Invalid questionId format rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 7: Reject non-existent question',
|
|
run: async () => {
|
|
try {
|
|
const fakeQuestionId = '00000000-0000-0000-0000-000000000000';
|
|
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
|
|
questionId: fakeQuestionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 404) {
|
|
throw new Error(`Expected 404, got ${error.response?.status}`);
|
|
}
|
|
if (!error.response.data.message.toLowerCase().includes('not found')) {
|
|
throw new Error('Error message should mention not found');
|
|
}
|
|
return '✓ Non-existent question rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 8: Reject invalid userId format',
|
|
run: async () => {
|
|
try {
|
|
await axios.post(`${API_URL}/users/invalid-uuid/bookmarks`, {
|
|
questionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 400) {
|
|
throw new Error(`Expected 400, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Invalid userId format rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 9: Reject non-existent user',
|
|
run: async () => {
|
|
try {
|
|
const fakeUserId = '00000000-0000-0000-0000-000000000000';
|
|
await axios.post(`${API_URL}/users/${fakeUserId}/bookmarks`, {
|
|
questionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been rejected');
|
|
} catch (error) {
|
|
if (error.response?.status !== 404) {
|
|
throw new Error(`Expected 404, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Non-existent user rejected';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 10: Cross-user bookmark addition blocked',
|
|
run: async () => {
|
|
try {
|
|
// Try to add bookmark to second user's account using first user's token
|
|
await axios.post(`${API_URL}/users/${secondUserId}/bookmarks`, {
|
|
questionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been blocked');
|
|
} catch (error) {
|
|
if (error.response?.status !== 403) {
|
|
throw new Error(`Expected 403, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Cross-user bookmark addition blocked';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 11: Cross-user bookmark removal blocked',
|
|
run: async () => {
|
|
try {
|
|
// Try to remove bookmark from second user's account using first user's token
|
|
await axios.delete(`${API_URL}/users/${secondUserId}/bookmarks/${questionId}`, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
throw new Error('Should have been blocked');
|
|
} catch (error) {
|
|
if (error.response?.status !== 403) {
|
|
throw new Error(`Expected 403, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Cross-user bookmark removal blocked';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 12: Unauthenticated add bookmark blocked',
|
|
run: async () => {
|
|
try {
|
|
await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
|
|
questionId
|
|
});
|
|
throw new Error('Should have been blocked');
|
|
} catch (error) {
|
|
if (error.response?.status !== 401) {
|
|
throw new Error(`Expected 401, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Unauthenticated add bookmark blocked';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 13: Unauthenticated remove bookmark blocked',
|
|
run: async () => {
|
|
try {
|
|
await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`);
|
|
throw new Error('Should have been blocked');
|
|
} catch (error) {
|
|
if (error.response?.status !== 401) {
|
|
throw new Error(`Expected 401, got ${error.response?.status}`);
|
|
}
|
|
return '✓ Unauthenticated remove bookmark blocked';
|
|
}
|
|
}
|
|
},
|
|
{
|
|
name: 'Test 14: Response structure validation',
|
|
run: async () => {
|
|
// Add a bookmark for testing response structure
|
|
const response = await axios.post(`${API_URL}/users/${userId}/bookmarks`, {
|
|
questionId
|
|
}, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
|
|
if (!response.data.success) throw new Error('success field missing');
|
|
if (!response.data.data) throw new Error('data field missing');
|
|
if (!response.data.data.id) throw new Error('bookmark id missing');
|
|
if (!response.data.data.questionId) throw new Error('questionId missing');
|
|
if (!response.data.data.question) throw new Error('question details missing');
|
|
if (!response.data.data.question.id) throw new Error('question.id missing');
|
|
if (!response.data.data.question.questionText) throw new Error('question.questionText missing');
|
|
if (!response.data.data.question.difficulty) throw new Error('question.difficulty missing');
|
|
if (!response.data.data.question.category) throw new Error('question.category missing');
|
|
if (!response.data.data.bookmarkedAt) throw new Error('bookmarkedAt missing');
|
|
if (!response.data.message) throw new Error('message field missing');
|
|
|
|
// Clean up
|
|
await axios.delete(`${API_URL}/users/${userId}/bookmarks/${questionId}`, {
|
|
headers: { 'Authorization': `Bearer ${userToken}` }
|
|
});
|
|
|
|
return '✓ Response structure valid';
|
|
}
|
|
}
|
|
];
|
|
|
|
// Run tests
|
|
async function runTests() {
|
|
console.log('============================================================');
|
|
console.log('BOOKMARK API TESTS');
|
|
console.log('============================================================\n');
|
|
|
|
await setup();
|
|
|
|
console.log('Running tests...\n');
|
|
|
|
let passed = 0;
|
|
let failed = 0;
|
|
|
|
for (const test of tests) {
|
|
try {
|
|
const result = await test.run();
|
|
console.log(result);
|
|
passed++;
|
|
} catch (error) {
|
|
console.log(`✗ ${test.name}`);
|
|
console.log(` Error: ${error.message}`);
|
|
if (error.response?.data) {
|
|
console.log(` Response:`, JSON.stringify(error.response.data, null, 2));
|
|
}
|
|
failed++;
|
|
}
|
|
// Small delay to avoid rate limiting
|
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
}
|
|
|
|
console.log('\n============================================================');
|
|
console.log(`RESULTS: ${passed} passed, ${failed} failed out of ${tests.length} tests`);
|
|
console.log('============================================================');
|
|
|
|
process.exit(failed > 0 ? 1 : 0);
|
|
}
|
|
|
|
runTests();
|