Add comprehensive database setup and user management system
- 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
This commit is contained in:
417
tests/test_metadata_functionality.html
Normal file
417
tests/test_metadata_functionality.html
Normal file
@ -0,0 +1,417 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Bookmark Metadata Functionality Test</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
.test-container {
|
||||
background: white;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.test-result {
|
||||
padding: 10px;
|
||||
margin: 5px 0;
|
||||
border-radius: 4px;
|
||||
font-weight: bold;
|
||||
}
|
||||
.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
.info {
|
||||
background-color: #d1ecf1;
|
||||
color: #0c5460;
|
||||
border: 1px solid #bee5eb;
|
||||
}
|
||||
.test-section {
|
||||
margin-bottom: 30px;
|
||||
padding: 15px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
}
|
||||
.test-section h3 {
|
||||
margin-top: 0;
|
||||
color: #2c3e50;
|
||||
}
|
||||
.bookmark-preview {
|
||||
background: #f8f9fa;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
margin: 10px 0;
|
||||
border-left: 4px solid #3498db;
|
||||
}
|
||||
.metadata-display {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
.tag {
|
||||
background: #e3f2fd;
|
||||
color: #1976d2;
|
||||
padding: 2px 6px;
|
||||
border-radius: 12px;
|
||||
font-size: 11px;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.rating {
|
||||
color: #ffc107;
|
||||
font-size: 16px;
|
||||
}
|
||||
.favorite {
|
||||
color: #e74c3c;
|
||||
font-size: 16px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-container">
|
||||
<h1>Bookmark Metadata Functionality Test</h1>
|
||||
<p>This page tests the new bookmark metadata and tagging system functionality.</p>
|
||||
|
||||
<div id="testResults"></div>
|
||||
|
||||
<div class="test-section">
|
||||
<h3>Test Bookmark with Metadata</h3>
|
||||
<div id="bookmarkPreview" class="bookmark-preview">
|
||||
<!-- Test bookmark will be displayed here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Create a test instance of BookmarkManager
|
||||
class TestBookmarkManager {
|
||||
constructor() {
|
||||
this.bookmarks = [];
|
||||
this.currentEditId = null;
|
||||
this.currentFilter = 'all';
|
||||
this.searchTimeout = null;
|
||||
this.virtualScrollThreshold = 100;
|
||||
this.itemsPerPage = 50;
|
||||
this.currentPage = 1;
|
||||
this.isLoading = false;
|
||||
|
||||
// Initialize with test data
|
||||
this.initializeTestData();
|
||||
}
|
||||
|
||||
initializeTestData() {
|
||||
// Create test bookmarks with metadata
|
||||
this.bookmarks = [
|
||||
{
|
||||
id: 1,
|
||||
title: "JavaScript Tutorial",
|
||||
url: "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
|
||||
folder: "Development",
|
||||
tags: ["javascript", "tutorial", "web-development", "programming"],
|
||||
notes: "Comprehensive JavaScript guide from MDN. Great for beginners and advanced developers.",
|
||||
rating: 5,
|
||||
favorite: true,
|
||||
addDate: Date.now() - 86400000, // 1 day ago
|
||||
lastModified: Date.now() - 3600000, // 1 hour ago
|
||||
lastVisited: Date.now() - 1800000, // 30 minutes ago
|
||||
icon: '',
|
||||
status: 'valid'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: "React Documentation",
|
||||
url: "https://reactjs.org/docs/getting-started.html",
|
||||
folder: "Development",
|
||||
tags: ["react", "frontend", "library"],
|
||||
notes: "Official React documentation with examples and best practices.",
|
||||
rating: 4,
|
||||
favorite: false,
|
||||
addDate: Date.now() - 172800000, // 2 days ago
|
||||
lastModified: Date.now() - 86400000, // 1 day ago
|
||||
lastVisited: null,
|
||||
icon: '',
|
||||
status: 'valid'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: "Favorite Recipe Blog",
|
||||
url: "https://example-recipes.com",
|
||||
folder: "Personal",
|
||||
tags: ["cooking", "recipes", "food"],
|
||||
notes: "Amazing collection of healthy recipes. Check the dessert section!",
|
||||
rating: 3,
|
||||
favorite: true,
|
||||
addDate: Date.now() - 259200000, // 3 days ago
|
||||
lastModified: Date.now() - 172800000, // 2 days ago
|
||||
lastVisited: Date.now() - 7200000, // 2 hours ago
|
||||
icon: '',
|
||||
status: 'unknown'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Helper function to format relative time
|
||||
formatRelativeTime(timestamp) {
|
||||
const now = Date.now();
|
||||
const diff = now - timestamp;
|
||||
|
||||
const minutes = Math.floor(diff / (1000 * 60));
|
||||
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||||
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (minutes < 60) {
|
||||
return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
|
||||
} else if (hours < 24) {
|
||||
return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
|
||||
} else {
|
||||
return `${days} day${days !== 1 ? 's' : ''} ago`;
|
||||
}
|
||||
}
|
||||
|
||||
// Test search functionality with metadata
|
||||
searchBookmarks(query) {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
return this.bookmarks.filter(bookmark => {
|
||||
if (bookmark.title.toLowerCase().includes(lowerQuery)) return true;
|
||||
if (bookmark.url.toLowerCase().includes(lowerQuery)) return true;
|
||||
if (bookmark.folder && bookmark.folder.toLowerCase().includes(lowerQuery)) return true;
|
||||
if (bookmark.tags && bookmark.tags.some(tag => tag.toLowerCase().includes(lowerQuery))) return true;
|
||||
if (bookmark.notes && bookmark.notes.toLowerCase().includes(lowerQuery)) return true;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
// Test filtering by favorites
|
||||
getFavoriteBookmarks() {
|
||||
return this.bookmarks.filter(b => b.favorite);
|
||||
}
|
||||
|
||||
// Test filtering by rating
|
||||
getBookmarksByRating(minRating) {
|
||||
return this.bookmarks.filter(b => b.rating >= minRating);
|
||||
}
|
||||
|
||||
// Test export functionality with metadata
|
||||
generateJSONExport(bookmarksToExport) {
|
||||
const exportData = {
|
||||
exportDate: new Date().toISOString(),
|
||||
version: '1.1',
|
||||
totalBookmarks: bookmarksToExport.length,
|
||||
bookmarks: bookmarksToExport.map(bookmark => ({
|
||||
id: bookmark.id,
|
||||
title: bookmark.title,
|
||||
url: bookmark.url,
|
||||
folder: bookmark.folder || '',
|
||||
tags: bookmark.tags || [],
|
||||
notes: bookmark.notes || '',
|
||||
rating: bookmark.rating || 0,
|
||||
favorite: bookmark.favorite || false,
|
||||
addDate: bookmark.addDate,
|
||||
lastModified: bookmark.lastModified,
|
||||
lastVisited: bookmark.lastVisited,
|
||||
icon: bookmark.icon || '',
|
||||
status: bookmark.status
|
||||
}))
|
||||
};
|
||||
|
||||
return JSON.stringify(exportData, null, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// Run tests
|
||||
function runTests() {
|
||||
const testManager = new TestBookmarkManager();
|
||||
let results = '<h2>Test Results</h2>';
|
||||
|
||||
// Test 1: Basic metadata storage
|
||||
try {
|
||||
const bookmark = testManager.bookmarks[0];
|
||||
const hasAllMetadata = bookmark.tags && bookmark.notes &&
|
||||
typeof bookmark.rating === 'number' &&
|
||||
typeof bookmark.favorite === 'boolean';
|
||||
|
||||
results += `
|
||||
<div class="test-result ${hasAllMetadata ? 'success' : 'error'}">
|
||||
Metadata Storage Test: ${hasAllMetadata ? 'PASSED' : 'FAILED'}
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
Metadata Storage Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 2: Search functionality with tags
|
||||
try {
|
||||
const searchResults = testManager.searchBookmarks('javascript');
|
||||
const foundByTag = searchResults.length > 0 &&
|
||||
searchResults.some(b => b.tags.includes('javascript'));
|
||||
|
||||
results += `
|
||||
<div class="test-result ${foundByTag ? 'success' : 'error'}">
|
||||
Tag Search Test: ${foundByTag ? 'PASSED' : 'FAILED'} (Found ${searchResults.length} results)
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
Tag Search Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 3: Search functionality with notes
|
||||
try {
|
||||
const searchResults = testManager.searchBookmarks('healthy recipes');
|
||||
const foundByNotes = searchResults.length > 0;
|
||||
|
||||
results += `
|
||||
<div class="test-result ${foundByNotes ? 'success' : 'error'}">
|
||||
Notes Search Test: ${foundByNotes ? 'PASSED' : 'FAILED'} (Found ${searchResults.length} results)
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
Notes Search Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 4: Favorite filtering
|
||||
try {
|
||||
const favorites = testManager.getFavoriteBookmarks();
|
||||
const correctFavorites = favorites.length === 2; // Should find 2 favorites
|
||||
|
||||
results += `
|
||||
<div class="test-result ${correctFavorites ? 'success' : 'error'}">
|
||||
Favorite Filter Test: ${correctFavorites ? 'PASSED' : 'FAILED'} (Found ${favorites.length} favorites)
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
Favorite Filter Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 5: Rating filtering
|
||||
try {
|
||||
const highRated = testManager.getBookmarksByRating(4);
|
||||
const correctRating = highRated.length === 2; // Should find 2 bookmarks with rating >= 4
|
||||
|
||||
results += `
|
||||
<div class="test-result ${correctRating ? 'success' : 'error'}">
|
||||
Rating Filter Test: ${correctRating ? 'PASSED' : 'FAILED'} (Found ${highRated.length} high-rated bookmarks)
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
Rating Filter Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 6: JSON export with metadata
|
||||
try {
|
||||
const jsonExport = testManager.generateJSONExport(testManager.bookmarks);
|
||||
const parsed = JSON.parse(jsonExport);
|
||||
const hasMetadataInExport = parsed.bookmarks[0].tags &&
|
||||
parsed.bookmarks[0].notes &&
|
||||
typeof parsed.bookmarks[0].rating === 'number' &&
|
||||
typeof parsed.bookmarks[0].favorite === 'boolean';
|
||||
|
||||
results += `
|
||||
<div class="test-result ${hasMetadataInExport ? 'success' : 'error'}">
|
||||
JSON Export Test: ${hasMetadataInExport ? 'PASSED' : 'FAILED'}
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
JSON Export Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// Test 7: Last visited tracking
|
||||
try {
|
||||
const bookmark = testManager.bookmarks[0];
|
||||
const hasLastVisited = bookmark.lastVisited && typeof bookmark.lastVisited === 'number';
|
||||
|
||||
results += `
|
||||
<div class="test-result ${hasLastVisited ? 'success' : 'error'}">
|
||||
Last Visited Tracking Test: ${hasLastVisited ? 'PASSED' : 'FAILED'}
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
results += `
|
||||
<div class="test-result error">
|
||||
Last Visited Tracking Test: FAILED - ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
document.getElementById('testResults').innerHTML = results;
|
||||
|
||||
// Display test bookmark with metadata
|
||||
displayTestBookmark(testManager.bookmarks[0], testManager);
|
||||
}
|
||||
|
||||
function displayTestBookmark(bookmark, manager) {
|
||||
const preview = document.getElementById('bookmarkPreview');
|
||||
|
||||
let html = `
|
||||
<h4>${bookmark.title}</h4>
|
||||
<div style="color: #666; margin-bottom: 10px;">${bookmark.url}</div>
|
||||
<div style="color: #3498db; font-size: 12px; margin-bottom: 10px;">Folder: ${bookmark.folder}</div>
|
||||
|
||||
<div class="metadata-display">
|
||||
<div style="margin-bottom: 8px;">
|
||||
<strong>Tags:</strong>
|
||||
${bookmark.tags.map(tag => `<span class="tag">${tag}</span>`).join('')}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
<strong>Rating:</strong>
|
||||
<span class="rating">${'★'.repeat(bookmark.rating)}${'☆'.repeat(5 - bookmark.rating)}</span>
|
||||
${bookmark.favorite ? '<span class="favorite">❤️ Favorite</span>' : ''}
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 8px;">
|
||||
<strong>Notes:</strong>
|
||||
<div style="font-style: italic; color: #666; margin-top: 4px;">${bookmark.notes}</div>
|
||||
</div>
|
||||
|
||||
<div style="font-size: 12px; color: #999;">
|
||||
<div>Added: ${manager.formatRelativeTime(bookmark.addDate)}</div>
|
||||
<div>Last modified: ${manager.formatRelativeTime(bookmark.lastModified)}</div>
|
||||
<div>Last visited: ${manager.formatRelativeTime(bookmark.lastVisited)}</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
preview.innerHTML = html;
|
||||
}
|
||||
|
||||
// Run tests when page loads
|
||||
document.addEventListener('DOMContentLoaded', runTests);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user