521 lines
17 KiB
JavaScript
521 lines
17 KiB
JavaScript
const axios = require('axios');
|
|
|
|
const BASE_URL = 'http://localhost:3000/api';
|
|
|
|
// Test user credentials
|
|
const testUser = {
|
|
username: 'bookmarklist1',
|
|
email: 'bookmarklist1@example.com',
|
|
password: 'Test123!@#'
|
|
};
|
|
|
|
const testUser2 = {
|
|
username: 'bookmarklist2',
|
|
email: 'bookmarklist2@example.com',
|
|
password: 'Test123!@#'
|
|
};
|
|
|
|
let userToken = '';
|
|
let userId = '';
|
|
let user2Token = '';
|
|
let user2Id = '';
|
|
let categoryId = '';
|
|
let questionIds = [];
|
|
let bookmarkIds = [];
|
|
|
|
// Test results tracking
|
|
let passedTests = 0;
|
|
let failedTests = 0;
|
|
const testResults = [];
|
|
|
|
// Helper function to log test results
|
|
function logTest(testName, passed, error = null) {
|
|
if (passed) {
|
|
console.log(`✓ ${testName}`);
|
|
passedTests++;
|
|
} else {
|
|
console.log(`✗ ${testName}`);
|
|
if (error) {
|
|
console.log(` Error: ${error}`);
|
|
}
|
|
failedTests++;
|
|
}
|
|
testResults.push({ testName, passed, error });
|
|
}
|
|
|
|
// Helper function to delay between tests
|
|
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
|
|
async function setup() {
|
|
try {
|
|
console.log('Setting up test data...');
|
|
|
|
// Register and login first user
|
|
try {
|
|
const regRes = await axios.post(`${BASE_URL}/auth/register`, testUser);
|
|
console.log('✓ Test user registered');
|
|
} catch (err) {
|
|
// User might already exist, continue to login
|
|
if (err.response?.status !== 409) {
|
|
console.log('Registration error:', err.response?.data?.message || err.message);
|
|
}
|
|
}
|
|
|
|
const loginRes = await axios.post(`${BASE_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 and login second user
|
|
try {
|
|
const regRes = await axios.post(`${BASE_URL}/auth/register`, testUser2);
|
|
console.log('✓ Second user registered');
|
|
} catch (err) {
|
|
// User might already exist, continue to login
|
|
if (err.response?.status !== 409) {
|
|
console.log('Registration error:', err.response?.data?.message || err.message);
|
|
}
|
|
}
|
|
|
|
const login2Res = await axios.post(`${BASE_URL}/auth/login`, {
|
|
email: testUser2.email,
|
|
password: testUser2.password
|
|
});
|
|
|
|
user2Token = login2Res.data.data.token;
|
|
user2Id = login2Res.data.data.user.id;
|
|
console.log('✓ Second user logged in');
|
|
|
|
// Get categories
|
|
const categoriesRes = await axios.get(`${BASE_URL}/categories`);
|
|
const categories = categoriesRes.data.data;
|
|
|
|
// Find a category with questions
|
|
let testCategory = null;
|
|
for (const cat of categories) {
|
|
if (cat.questionCount > 0) {
|
|
testCategory = cat;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!testCategory) {
|
|
throw new Error('No categories with questions found');
|
|
}
|
|
|
|
categoryId = testCategory.id;
|
|
console.log(`✓ Found test category: ${testCategory.name} (${testCategory.questionCount} questions)`);
|
|
|
|
// Get questions from this category
|
|
const questionsRes = await axios.get(`${BASE_URL}/questions/category/${categoryId}?limit=10`);
|
|
const questions = questionsRes.data.data;
|
|
|
|
if (questions.length === 0) {
|
|
throw new Error('No questions available in category for testing');
|
|
}
|
|
|
|
questionIds = questions.slice(0, Math.min(5, questions.length)).map(q => q.id);
|
|
console.log(`✓ Found ${questionIds.length} test questions`);
|
|
|
|
// Delete any existing bookmarks first (cleanup from previous test runs)
|
|
for (const questionId of questionIds) {
|
|
try {
|
|
await axios.delete(
|
|
`${BASE_URL}/users/${userId}/bookmarks/${questionId}`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
} catch (err) {
|
|
// Ignore if bookmark doesn't exist
|
|
}
|
|
}
|
|
|
|
// Create bookmarks for testing
|
|
for (const questionId of questionIds) {
|
|
const bookmarkRes = await axios.post(
|
|
`${BASE_URL}/users/${userId}/bookmarks`,
|
|
{ questionId },
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
bookmarkIds.push(bookmarkRes.data.data.id);
|
|
await delay(100);
|
|
}
|
|
console.log(`✓ Created ${bookmarkIds.length} bookmarks for testing`);
|
|
|
|
} catch (error) {
|
|
console.error('Setup failed:', error.response?.data || error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function runTests() {
|
|
console.log('\n============================================================');
|
|
console.log('USER BOOKMARKS API TESTS');
|
|
console.log('============================================================\n');
|
|
|
|
await setup();
|
|
|
|
console.log('\nRunning tests...');
|
|
|
|
// Test 1: Get bookmarks with default pagination
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.success === true &&
|
|
Array.isArray(response.data.data.bookmarks) &&
|
|
response.data.data.bookmarks.length > 0 &&
|
|
response.data.data.pagination.currentPage === 1 &&
|
|
response.data.data.pagination.itemsPerPage === 10;
|
|
|
|
logTest('Get bookmarks with default pagination', passed);
|
|
} catch (error) {
|
|
logTest('Get bookmarks with default pagination', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 2: Pagination structure validation
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?page=1&limit=5`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const pagination = response.data.data.pagination;
|
|
const passed = response.status === 200 &&
|
|
pagination.currentPage === 1 &&
|
|
pagination.itemsPerPage === 5 &&
|
|
typeof pagination.totalPages === 'number' &&
|
|
typeof pagination.totalItems === 'number' &&
|
|
typeof pagination.hasNextPage === 'boolean' &&
|
|
typeof pagination.hasPreviousPage === 'boolean';
|
|
|
|
logTest('Pagination structure validation', passed);
|
|
} catch (error) {
|
|
logTest('Pagination structure validation', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 3: Bookmark fields validation
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const bookmark = response.data.data.bookmarks[0];
|
|
const passed = response.status === 200 &&
|
|
bookmark.bookmarkId &&
|
|
bookmark.bookmarkedAt &&
|
|
bookmark.question &&
|
|
bookmark.question.id &&
|
|
bookmark.question.questionText &&
|
|
bookmark.question.difficulty &&
|
|
bookmark.question.category &&
|
|
bookmark.question.statistics;
|
|
|
|
logTest('Bookmark fields validation', passed);
|
|
} catch (error) {
|
|
logTest('Bookmark fields validation', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 4: Custom limit
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?limit=2`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.data.bookmarks.length <= 2 &&
|
|
response.data.data.pagination.itemsPerPage === 2;
|
|
|
|
logTest('Custom limit', passed);
|
|
} catch (error) {
|
|
logTest('Custom limit', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 5: Page 2 navigation
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?page=2&limit=3`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.data.pagination.currentPage === 2 &&
|
|
response.data.data.pagination.hasPreviousPage === true;
|
|
|
|
logTest('Page 2 navigation', passed);
|
|
} catch (error) {
|
|
logTest('Page 2 navigation', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 6: Category filter
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?category=${categoryId}`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const allMatchCategory = response.data.data.bookmarks.every(
|
|
b => b.question.category.id === categoryId
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
allMatchCategory &&
|
|
response.data.data.filters.category === categoryId;
|
|
|
|
logTest('Category filter', passed);
|
|
} catch (error) {
|
|
logTest('Category filter', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 7: Difficulty filter
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?difficulty=medium`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const allMatchDifficulty = response.data.data.bookmarks.every(
|
|
b => b.question.difficulty === 'medium'
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
(response.data.data.bookmarks.length === 0 || allMatchDifficulty) &&
|
|
response.data.data.filters.difficulty === 'medium';
|
|
|
|
logTest('Difficulty filter', passed);
|
|
} catch (error) {
|
|
logTest('Difficulty filter', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 8: Sort by difficulty ascending
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?sortBy=difficulty&sortOrder=asc`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.data.sorting.sortBy === 'difficulty' &&
|
|
response.data.data.sorting.sortOrder === 'asc';
|
|
|
|
logTest('Sort by difficulty ascending', passed);
|
|
} catch (error) {
|
|
logTest('Sort by difficulty ascending', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 9: Sort by date descending (default)
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?sortBy=date&sortOrder=desc`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.data.sorting.sortBy === 'date' &&
|
|
response.data.data.sorting.sortOrder === 'desc';
|
|
|
|
logTest('Sort by date descending', passed);
|
|
} catch (error) {
|
|
logTest('Sort by date descending', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 10: Max limit enforcement (50)
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?limit=100`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.data.pagination.itemsPerPage === 50;
|
|
|
|
logTest('Max limit enforcement (50)', passed);
|
|
} catch (error) {
|
|
logTest('Max limit enforcement (50)', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 11: Cross-user access blocked
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${user2Id}/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
logTest('Cross-user access blocked', false, 'Expected 403 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 403;
|
|
logTest('Cross-user access blocked', passed, passed ? null : `Expected 403 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 12: Unauthenticated request blocked
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks`
|
|
);
|
|
|
|
logTest('Unauthenticated request blocked', false, 'Expected 401 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 401;
|
|
logTest('Unauthenticated request blocked', passed, passed ? null : `Expected 401 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 13: Invalid UUID format
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/invalid-uuid/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
logTest('Invalid UUID format', false, 'Expected 400 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 400;
|
|
logTest('Invalid UUID format', passed, passed ? null : `Expected 400 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 14: Non-existent user
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/11111111-1111-1111-1111-111111111111/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
logTest('Non-existent user', false, 'Expected 404 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 404;
|
|
logTest('Non-existent user', passed, passed ? null : `Expected 404 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 15: Invalid category ID format
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?category=invalid-uuid`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
logTest('Invalid category ID format', false, 'Expected 400 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 400;
|
|
logTest('Invalid category ID format', passed, passed ? null : `Expected 400 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 16: Invalid difficulty value
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?difficulty=invalid`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
logTest('Invalid difficulty value', false, 'Expected 400 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 400;
|
|
logTest('Invalid difficulty value', passed, passed ? null : `Expected 400 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 17: Invalid sort order
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?sortOrder=invalid`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
logTest('Invalid sort order', false, 'Expected 400 but got 200');
|
|
} catch (error) {
|
|
const passed = error.response?.status === 400;
|
|
logTest('Invalid sort order', passed, passed ? null : `Expected 400 but got ${error.response?.status}`);
|
|
}
|
|
|
|
// Test 18: Empty bookmarks list
|
|
await delay(100);
|
|
try {
|
|
// Use second user who has no bookmarks
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${user2Id}/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${user2Token}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
Array.isArray(response.data.data.bookmarks) &&
|
|
response.data.data.bookmarks.length === 0 &&
|
|
response.data.data.pagination.totalItems === 0;
|
|
|
|
logTest('Empty bookmarks list', passed);
|
|
} catch (error) {
|
|
logTest('Empty bookmarks list', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 19: Combined filters (category + difficulty + sorting)
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks?category=${categoryId}&difficulty=easy&sortBy=date&sortOrder=asc`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const passed = response.status === 200 &&
|
|
response.data.data.filters.category === categoryId &&
|
|
response.data.data.filters.difficulty === 'easy' &&
|
|
response.data.data.sorting.sortBy === 'date' &&
|
|
response.data.data.sorting.sortOrder === 'asc';
|
|
|
|
logTest('Combined filters', passed);
|
|
} catch (error) {
|
|
logTest('Combined filters', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Test 20: Question statistics included
|
|
await delay(100);
|
|
try {
|
|
const response = await axios.get(
|
|
`${BASE_URL}/users/${userId}/bookmarks`,
|
|
{ headers: { Authorization: `Bearer ${userToken}` } }
|
|
);
|
|
|
|
const bookmark = response.data.data.bookmarks[0];
|
|
const stats = bookmark.question.statistics;
|
|
const passed = response.status === 200 &&
|
|
stats &&
|
|
typeof stats.timesAttempted === 'number' &&
|
|
typeof stats.timesCorrect === 'number' &&
|
|
typeof stats.accuracy === 'number';
|
|
|
|
logTest('Question statistics included', passed);
|
|
} catch (error) {
|
|
logTest('Question statistics included', false, error.response?.data?.message || error.message);
|
|
}
|
|
|
|
// Print results
|
|
console.log('\n============================================================');
|
|
console.log(`RESULTS: ${passedTests} passed, ${failedTests} failed out of ${passedTests + failedTests} tests`);
|
|
console.log('============================================================\n');
|
|
|
|
process.exit(failedTests > 0 ? 1 : 0);
|
|
}
|
|
|
|
// Run tests
|
|
runTests();
|