- Implement PostgreSQL database schema with users and bookmarks tables - Add database connection pooling with retry logic and error handling - Create migration system with automatic schema initialization - Add database CLI tools for management (init, status, validate, etc.) - Include comprehensive error handling and diagnostics - Add development seed data and testing utilities - Implement health monitoring and connection pool statistics - Create detailed documentation and troubleshooting guide Database features: - Users table with authentication fields and email verification - Bookmarks table with user association and metadata - Proper indexes for performance optimization - Automatic timestamp triggers - Transaction support with rollback handling - Connection pooling (20 max connections, 30s idle timeout) - Graceful shutdown handling CLI commands available: - npm run db:init - Initialize database - npm run db:status - Check database status - npm run db:validate - Validate schema - npm run db:test - Run database tests - npm run db:diagnostics - Full diagnostics
309 lines
13 KiB
HTML
309 lines
13 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Advanced Search Test</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; margin: 20px; }
|
|
.test-section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; }
|
|
.test-result { margin: 10px 0; padding: 10px; }
|
|
.pass { background-color: #d4edda; color: #155724; }
|
|
.fail { background-color: #f8d7da; color: #721c24; }
|
|
button { margin: 5px; padding: 10px 15px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<h1>Advanced Search Functionality Test</h1>
|
|
|
|
<div class="test-section">
|
|
<h2>Test 1: Search within specific folders</h2>
|
|
<button onclick="testFolderSearch()">Test Folder Search</button>
|
|
<div id="folderSearchResult" class="test-result"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>Test 2: Date-based filtering</h2>
|
|
<button onclick="testDateFiltering()">Test Date Filtering</button>
|
|
<div id="dateFilterResult" class="test-result"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>Test 3: Search suggestions</h2>
|
|
<button onclick="testSearchSuggestions()">Test Search Suggestions</button>
|
|
<div id="suggestionsResult" class="test-result"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>Test 4: Search history</h2>
|
|
<button onclick="testSearchHistory()">Test Search History</button>
|
|
<div id="historyResult" class="test-result"></div>
|
|
</div>
|
|
|
|
<div class="test-section">
|
|
<h2>Test 5: Saved searches</h2>
|
|
<button onclick="testSavedSearches()">Test Saved Searches</button>
|
|
<div id="savedSearchResult" class="test-result"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// Mock BookmarkManager for testing
|
|
class MockBookmarkManager {
|
|
constructor() {
|
|
this.bookmarks = [
|
|
{
|
|
id: 1,
|
|
title: 'Google',
|
|
url: 'https://google.com',
|
|
folder: 'Search Engines',
|
|
addDate: Date.now() - 86400000, // 1 day ago
|
|
status: 'valid'
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'GitHub',
|
|
url: 'https://github.com',
|
|
folder: 'Development',
|
|
addDate: Date.now() - 604800000, // 1 week ago
|
|
status: 'valid'
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'Stack Overflow',
|
|
url: 'https://stackoverflow.com',
|
|
folder: 'Development',
|
|
addDate: Date.now() - 2592000000, // 1 month ago
|
|
status: 'valid'
|
|
}
|
|
];
|
|
this.searchHistory = [];
|
|
this.savedSearches = [];
|
|
}
|
|
|
|
// Test folder search functionality
|
|
executeAdvancedSearch(criteria) {
|
|
let results = [...this.bookmarks];
|
|
|
|
if (criteria.query) {
|
|
const lowerQuery = criteria.query.toLowerCase();
|
|
results = results.filter(bookmark => {
|
|
return bookmark.title.toLowerCase().includes(lowerQuery) ||
|
|
bookmark.url.toLowerCase().includes(lowerQuery) ||
|
|
(bookmark.folder && bookmark.folder.toLowerCase().includes(lowerQuery));
|
|
});
|
|
}
|
|
|
|
if (criteria.folder) {
|
|
results = results.filter(bookmark => bookmark.folder === criteria.folder);
|
|
}
|
|
|
|
if (criteria.dateFilter) {
|
|
results = this.applyDateFilter(results, criteria);
|
|
}
|
|
|
|
if (criteria.status) {
|
|
results = results.filter(bookmark => bookmark.status === criteria.status);
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
applyDateFilter(bookmarks, criteria) {
|
|
const now = new Date();
|
|
let startDate, endDate;
|
|
|
|
switch (criteria.dateFilter) {
|
|
case 'today':
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
endDate = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
break;
|
|
case 'week':
|
|
const weekStart = new Date(now);
|
|
weekStart.setDate(now.getDate() - now.getDay());
|
|
weekStart.setHours(0, 0, 0, 0);
|
|
startDate = weekStart;
|
|
endDate = new Date(weekStart);
|
|
endDate.setDate(weekStart.getDate() + 7);
|
|
break;
|
|
case 'month':
|
|
startDate = new Date(now.getFullYear(), now.getMonth(), 1);
|
|
endDate = new Date(now.getFullYear(), now.getMonth() + 1, 1);
|
|
break;
|
|
default:
|
|
return bookmarks;
|
|
}
|
|
|
|
return bookmarks.filter(bookmark => {
|
|
const bookmarkDate = new Date(bookmark.addDate);
|
|
const afterStart = !startDate || bookmarkDate >= startDate;
|
|
const beforeEnd = !endDate || bookmarkDate < endDate;
|
|
return afterStart && beforeEnd;
|
|
});
|
|
}
|
|
|
|
generateSearchSuggestions(query) {
|
|
const lowerQuery = query.toLowerCase();
|
|
const suggestions = new Set();
|
|
|
|
this.bookmarks.forEach(bookmark => {
|
|
if (bookmark.title.toLowerCase().includes(lowerQuery)) {
|
|
suggestions.add(bookmark.title);
|
|
}
|
|
if (bookmark.folder && bookmark.folder.toLowerCase().includes(lowerQuery)) {
|
|
suggestions.add(bookmark.folder);
|
|
}
|
|
});
|
|
|
|
return Array.from(suggestions).slice(0, 10);
|
|
}
|
|
|
|
addToSearchHistory(criteria) {
|
|
this.searchHistory.unshift(criteria);
|
|
if (this.searchHistory.length > 20) {
|
|
this.searchHistory = this.searchHistory.slice(0, 20);
|
|
}
|
|
}
|
|
|
|
saveSearch(name, criteria) {
|
|
const savedSearch = {
|
|
id: Date.now(),
|
|
name: name,
|
|
criteria: criteria,
|
|
createdAt: Date.now()
|
|
};
|
|
this.savedSearches.push(savedSearch);
|
|
return savedSearch;
|
|
}
|
|
}
|
|
|
|
const mockManager = new MockBookmarkManager();
|
|
|
|
function testFolderSearch() {
|
|
const result = document.getElementById('folderSearchResult');
|
|
|
|
try {
|
|
// Test searching within Development folder
|
|
const criteria = { folder: 'Development' };
|
|
const results = mockManager.executeAdvancedSearch(criteria);
|
|
|
|
const expectedCount = 2; // GitHub and Stack Overflow
|
|
const actualCount = results.length;
|
|
|
|
if (actualCount === expectedCount) {
|
|
result.className = 'test-result pass';
|
|
result.innerHTML = `✓ PASS: Found ${actualCount} bookmarks in Development folder (expected ${expectedCount})`;
|
|
} else {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Found ${actualCount} bookmarks in Development folder (expected ${expectedCount})`;
|
|
}
|
|
} catch (error) {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Error during folder search test: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
function testDateFiltering() {
|
|
const result = document.getElementById('dateFilterResult');
|
|
|
|
try {
|
|
// Test filtering by this week
|
|
const criteria = { dateFilter: 'week' };
|
|
const results = mockManager.executeAdvancedSearch(criteria);
|
|
|
|
// Should find Google (1 day ago) and GitHub (1 week ago)
|
|
const expectedCount = 2;
|
|
const actualCount = results.length;
|
|
|
|
if (actualCount === expectedCount) {
|
|
result.className = 'test-result pass';
|
|
result.innerHTML = `✓ PASS: Found ${actualCount} bookmarks from this week (expected ${expectedCount})`;
|
|
} else {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Found ${actualCount} bookmarks from this week (expected ${expectedCount})`;
|
|
}
|
|
} catch (error) {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Error during date filtering test: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
function testSearchSuggestions() {
|
|
const result = document.getElementById('suggestionsResult');
|
|
|
|
try {
|
|
const suggestions = mockManager.generateSearchSuggestions('dev');
|
|
const expectedSuggestions = ['Development'];
|
|
|
|
const hasExpectedSuggestion = suggestions.includes('Development');
|
|
|
|
if (hasExpectedSuggestion) {
|
|
result.className = 'test-result pass';
|
|
result.innerHTML = `✓ PASS: Generated suggestions: ${suggestions.join(', ')}`;
|
|
} else {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Expected 'Development' in suggestions, got: ${suggestions.join(', ')}`;
|
|
}
|
|
} catch (error) {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Error during search suggestions test: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
function testSearchHistory() {
|
|
const result = document.getElementById('historyResult');
|
|
|
|
try {
|
|
const criteria = { query: 'test search', folder: 'Development' };
|
|
mockManager.addToSearchHistory(criteria);
|
|
|
|
const historyCount = mockManager.searchHistory.length;
|
|
const latestSearch = mockManager.searchHistory[0];
|
|
|
|
if (historyCount === 1 && latestSearch.query === 'test search') {
|
|
result.className = 'test-result pass';
|
|
result.innerHTML = `✓ PASS: Search history working correctly (${historyCount} items)`;
|
|
} else {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Search history not working correctly`;
|
|
}
|
|
} catch (error) {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Error during search history test: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
function testSavedSearches() {
|
|
const result = document.getElementById('savedSearchResult');
|
|
|
|
try {
|
|
const criteria = { query: 'saved search', folder: 'Development' };
|
|
const savedSearch = mockManager.saveSearch('My Test Search', criteria);
|
|
|
|
const savedCount = mockManager.savedSearches.length;
|
|
|
|
if (savedCount === 1 && savedSearch.name === 'My Test Search') {
|
|
result.className = 'test-result pass';
|
|
result.innerHTML = `✓ PASS: Saved searches working correctly (${savedCount} saved)`;
|
|
} else {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Saved searches not working correctly`;
|
|
}
|
|
} catch (error) {
|
|
result.className = 'test-result fail';
|
|
result.innerHTML = `✗ FAIL: Error during saved searches test: ${error.message}`;
|
|
}
|
|
}
|
|
|
|
// Run all tests automatically
|
|
window.onload = function() {
|
|
setTimeout(() => {
|
|
testFolderSearch();
|
|
testDateFiltering();
|
|
testSearchSuggestions();
|
|
testSearchHistory();
|
|
testSavedSearches();
|
|
}, 100);
|
|
};
|
|
</script>
|
|
</body>
|
|
</html> |