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:
270
tests/verify_advanced_import.js
Normal file
270
tests/verify_advanced_import.js
Normal file
@ -0,0 +1,270 @@
|
||||
// Verification script for advanced import/export functionality
|
||||
// This script tests the new features without requiring a browser
|
||||
|
||||
// Mock DOM elements and browser APIs
|
||||
global.document = {
|
||||
getElementById: (id) => ({
|
||||
value: '',
|
||||
checked: true,
|
||||
style: { display: 'none' },
|
||||
innerHTML: '',
|
||||
textContent: '',
|
||||
addEventListener: () => {},
|
||||
classList: { add: () => {}, remove: () => {} },
|
||||
setAttribute: () => {},
|
||||
getAttribute: () => null
|
||||
}),
|
||||
querySelectorAll: () => [],
|
||||
querySelector: () => null,
|
||||
createElement: () => ({
|
||||
className: '',
|
||||
innerHTML: '',
|
||||
appendChild: () => {},
|
||||
addEventListener: () => {}
|
||||
})
|
||||
};
|
||||
|
||||
global.window = {
|
||||
open: () => {},
|
||||
addEventListener: () => {}
|
||||
};
|
||||
|
||||
global.localStorage = {
|
||||
getItem: () => null,
|
||||
setItem: () => {},
|
||||
removeItem: () => {}
|
||||
};
|
||||
|
||||
global.alert = console.log;
|
||||
global.confirm = () => true;
|
||||
global.prompt = () => 'test';
|
||||
|
||||
// Mock FileReader
|
||||
global.FileReader = class {
|
||||
readAsText() {
|
||||
setTimeout(() => {
|
||||
this.onload({ target: { result: '{"test": "data"}' } });
|
||||
}, 10);
|
||||
}
|
||||
};
|
||||
|
||||
// Mock DOMParser
|
||||
global.DOMParser = class {
|
||||
parseFromString(content, type) {
|
||||
return {
|
||||
querySelectorAll: () => [],
|
||||
querySelector: () => null
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
global.btoa = (str) => Buffer.from(str).toString('base64');
|
||||
global.atob = (str) => Buffer.from(str, 'base64').toString();
|
||||
|
||||
// Load the BookmarkManager class
|
||||
const fs = require('fs');
|
||||
const scriptContent = fs.readFileSync('script.js', 'utf8');
|
||||
|
||||
// Extract just the BookmarkManager class definition
|
||||
const classMatch = scriptContent.match(/class BookmarkManager \{[\s\S]*?\n\}/);
|
||||
if (!classMatch) {
|
||||
console.error('Could not extract BookmarkManager class');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Evaluate the class
|
||||
eval(classMatch[0]);
|
||||
|
||||
// Test the advanced import functionality
|
||||
async function testAdvancedImport() {
|
||||
console.log('🧪 Testing Advanced Import/Export Features...\n');
|
||||
|
||||
const manager = new BookmarkManager();
|
||||
|
||||
// Test 1: Chrome bookmarks parsing
|
||||
console.log('1️⃣ Testing Chrome bookmarks parsing...');
|
||||
try {
|
||||
const sampleChromeData = {
|
||||
"roots": {
|
||||
"bookmark_bar": {
|
||||
"children": [
|
||||
{
|
||||
"type": "url",
|
||||
"name": "Google",
|
||||
"url": "https://www.google.com",
|
||||
"date_added": "13285166270000000"
|
||||
},
|
||||
{
|
||||
"type": "folder",
|
||||
"name": "Development",
|
||||
"children": [
|
||||
{
|
||||
"type": "url",
|
||||
"name": "GitHub",
|
||||
"url": "https://github.com",
|
||||
"date_added": "13285166280000000"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const bookmarks = manager.parseChromeBookmarks(JSON.stringify(sampleChromeData));
|
||||
console.log(` ✅ Parsed ${bookmarks.length} bookmarks from Chrome format`);
|
||||
console.log(` 📁 Folders: ${[...new Set(bookmarks.map(b => b.folder || 'Root'))].join(', ')}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Chrome parsing failed: ${error.message}`);
|
||||
}
|
||||
|
||||
// Test 2: Firefox bookmarks parsing
|
||||
console.log('\n2️⃣ Testing Firefox bookmarks parsing...');
|
||||
try {
|
||||
const sampleFirefoxData = [
|
||||
{
|
||||
"type": "text/x-moz-place-container",
|
||||
"title": "Bookmarks Menu",
|
||||
"children": [
|
||||
{
|
||||
"type": "text/x-moz-place",
|
||||
"title": "Mozilla Firefox",
|
||||
"uri": "https://www.mozilla.org/firefox/",
|
||||
"dateAdded": 1642534567890000
|
||||
},
|
||||
{
|
||||
"type": "text/x-moz-place-container",
|
||||
"title": "Development",
|
||||
"children": [
|
||||
{
|
||||
"type": "text/x-moz-place",
|
||||
"title": "MDN Web Docs",
|
||||
"uri": "https://developer.mozilla.org/",
|
||||
"dateAdded": 1642534577890000
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const bookmarks = manager.parseFirefoxBookmarks(JSON.stringify(sampleFirefoxData));
|
||||
console.log(` ✅ Parsed ${bookmarks.length} bookmarks from Firefox format`);
|
||||
console.log(` 📁 Folders: ${[...new Set(bookmarks.map(b => b.folder || 'Root'))].join(', ')}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Firefox parsing failed: ${error.message}`);
|
||||
}
|
||||
|
||||
// Test 3: Format detection
|
||||
console.log('\n3️⃣ Testing format detection...');
|
||||
try {
|
||||
const chromeFormat = manager.detectFileFormat(
|
||||
{ name: 'bookmarks.json' },
|
||||
JSON.stringify({ roots: { bookmark_bar: {} } })
|
||||
);
|
||||
const firefoxFormat = manager.detectFileFormat(
|
||||
{ name: 'bookmarks.json' },
|
||||
JSON.stringify([{ type: 'text/x-moz-place-container' }])
|
||||
);
|
||||
const htmlFormat = manager.detectFileFormat(
|
||||
{ name: 'bookmarks.html' },
|
||||
'<!DOCTYPE NETSCAPE-Bookmark-file-1>'
|
||||
);
|
||||
|
||||
console.log(` ✅ Chrome format detected: ${chromeFormat === 'chrome'}`);
|
||||
console.log(` ✅ Firefox format detected: ${firefoxFormat === 'firefox'}`);
|
||||
console.log(` ✅ HTML format detected: ${htmlFormat === 'netscape'}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Format detection failed: ${error.message}`);
|
||||
}
|
||||
|
||||
// Test 4: Duplicate detection
|
||||
console.log('\n4️⃣ Testing enhanced duplicate detection...');
|
||||
try {
|
||||
// Add some existing bookmarks
|
||||
manager.bookmarks = [
|
||||
{
|
||||
id: 'existing1',
|
||||
title: 'Google Search Engine',
|
||||
url: 'https://www.google.com/',
|
||||
folder: 'Search',
|
||||
addDate: Date.now() - 1000000,
|
||||
status: 'valid'
|
||||
}
|
||||
];
|
||||
|
||||
const testBookmark = {
|
||||
title: 'Google',
|
||||
url: 'https://google.com',
|
||||
folder: 'Web',
|
||||
addDate: Date.now(),
|
||||
status: 'unknown'
|
||||
};
|
||||
|
||||
const duplicate = manager.findDuplicateBookmark(testBookmark, true, true);
|
||||
const similarity = manager.calculateStringSimilarity('Google Search Engine', 'Google');
|
||||
|
||||
console.log(` ✅ Duplicate detection working: ${duplicate ? 'Found duplicate' : 'No duplicate'}`);
|
||||
console.log(` 📊 Title similarity: ${Math.round(similarity * 100)}%`);
|
||||
console.log(` 🔗 URL normalization: ${manager.normalizeUrl('https://www.google.com/') === manager.normalizeUrl('https://google.com')}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Duplicate detection failed: ${error.message}`);
|
||||
}
|
||||
|
||||
// Test 5: Import analysis
|
||||
console.log('\n5️⃣ Testing import analysis...');
|
||||
try {
|
||||
const importData = {
|
||||
bookmarks: [
|
||||
{
|
||||
id: 'import1',
|
||||
title: 'New Site',
|
||||
url: 'https://example.com',
|
||||
folder: 'Examples',
|
||||
addDate: Date.now(),
|
||||
status: 'unknown'
|
||||
},
|
||||
{
|
||||
id: 'import2',
|
||||
title: 'Google Clone',
|
||||
url: 'https://google.com',
|
||||
folder: 'Search',
|
||||
addDate: Date.now(),
|
||||
status: 'unknown'
|
||||
}
|
||||
],
|
||||
format: 'test',
|
||||
originalCount: 2
|
||||
};
|
||||
|
||||
const analysis = manager.analyzeImportData(importData, 'merge');
|
||||
console.log(` ✅ Analysis completed:`);
|
||||
console.log(` 📊 New bookmarks: ${analysis.newBookmarks.length}`);
|
||||
console.log(` 🔄 Duplicates: ${analysis.duplicates.length}`);
|
||||
console.log(` 📁 Folders: ${analysis.folders.length}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Import analysis failed: ${error.message}`);
|
||||
}
|
||||
|
||||
// Test 6: Sync functionality
|
||||
console.log('\n6️⃣ Testing sync functionality...');
|
||||
try {
|
||||
const deviceId = manager.getDeviceId();
|
||||
const syncData = manager.exportForSync();
|
||||
const dataHash = manager.calculateDataHash();
|
||||
const compressed = manager.compressAndEncodeData({ test: 'data' });
|
||||
const decompressed = manager.decodeAndDecompressData(compressed);
|
||||
|
||||
console.log(` ✅ Device ID generated: ${deviceId.startsWith('device_')}`);
|
||||
console.log(` 📦 Sync export working: ${syncData.bookmarks.length >= 0}`);
|
||||
console.log(` 🔢 Data hash generated: ${typeof dataHash === 'string'}`);
|
||||
console.log(` 🗜️ Compression working: ${decompressed.test === 'data'}`);
|
||||
} catch (error) {
|
||||
console.log(` ❌ Sync functionality failed: ${error.message}`);
|
||||
}
|
||||
|
||||
console.log('\n🎉 Advanced Import/Export Features Testing Complete!');
|
||||
}
|
||||
|
||||
// Run the tests
|
||||
testAdvancedImport().catch(console.error);
|
||||
Reference in New Issue
Block a user