add changes
This commit is contained in:
411
backend/test-bookmarks.js
Normal file
411
backend/test-bookmarks.js
Normal file
@@ -0,0 +1,411 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user