Files
bookmarksite/tests/test_integration.html
Rainer Koschnick 0abee5b794 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
2025-07-19 23:21:50 +02:00

508 lines
24 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 Import Integration Test</title>
<link rel="stylesheet" href="styles.css">
<style>
.test-container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
.test-section {
margin: 30px 0;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
background: #f9f9f9;
}
.test-result {
margin: 15px 0;
padding: 10px;
border-radius: 4px;
}
.success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.error { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
.info { background: #d1ecf1; color: #0c5460; border: 1px solid #bee5eb; }
.sample-data {
background: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 4px;
padding: 10px;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
margin: 10px 0;
}
</style>
</head>
<body>
<div class="test-container">
<h1>Advanced Import/Export Integration Test</h1>
<p>This page tests the advanced import/export features integrated with the main bookmark manager.</p>
<div class="test-section">
<h2>Test 1: Enhanced Import Modal</h2>
<p>Test the new import modal with multiple format support and preview functionality.</p>
<button id="testImportModal" class="btn btn-primary">Open Import Modal</button>
<div id="importModalResult" class="test-result"></div>
</div>
<div class="test-section">
<h2>Test 2: Chrome Bookmarks Import</h2>
<p>Test importing Chrome bookmarks JSON format.</p>
<div class="sample-data">
<strong>Sample Chrome Data:</strong><br>
{"roots":{"bookmark_bar":{"children":[{"type":"url","name":"Google","url":"https://www.google.com"},{"type":"folder","name":"Dev","children":[{"type":"url","name":"GitHub","url":"https://github.com"}]}]}}}
</div>
<button id="testChromeImport" class="btn btn-primary">Test Chrome Import</button>
<div id="chromeImportResult" class="test-result"></div>
</div>
<div class="test-section">
<h2>Test 3: Firefox Bookmarks Import</h2>
<p>Test importing Firefox bookmarks JSON format.</p>
<div class="sample-data">
<strong>Sample Firefox Data:</strong><br>
[{"type":"text/x-moz-place-container","title":"Menu","children":[{"type":"text/x-moz-place","title":"Mozilla","uri":"https://mozilla.org"}]}]
</div>
<button id="testFirefoxImport" class="btn btn-primary">Test Firefox Import</button>
<div id="firefoxImportResult" class="test-result"></div>
</div>
<div class="test-section">
<h2>Test 4: Import Preview</h2>
<p>Test the import preview functionality with duplicate detection.</p>
<button id="testImportPreview" class="btn btn-primary">Test Import Preview</button>
<div id="previewResult" class="test-result"></div>
</div>
<div class="test-section">
<h2>Test 5: Incremental Import</h2>
<p>Test incremental import with smart duplicate handling.</p>
<button id="testIncrementalImport" class="btn btn-primary">Test Incremental Import</button>
<div id="incrementalResult" class="test-result"></div>
</div>
<div class="test-section">
<h2>Test 6: Device Sync Features</h2>
<p>Test the device synchronization functionality.</p>
<button id="testSyncFeatures" class="btn btn-primary">Test Sync Features</button>
<div id="syncResult" class="test-result"></div>
</div>
<div class="test-section">
<h2>Current Bookmarks</h2>
<p>View current bookmarks in the manager:</p>
<button id="showBookmarks" class="btn btn-secondary">Show Current Bookmarks</button>
<div id="bookmarksDisplay" class="test-result"></div>
</div>
</div>
<!-- Include the main application -->
<div style="display: none;">
<div id="importModal" class="modal">
<div class="modal-content advanced-import-content">
<button class="close">&times;</button>
<h2>Import Bookmarks</h2>
<div class="import-tabs">
<button class="import-tab active" data-tab="file">File Import</button>
<button class="import-tab" data-tab="sync">Device Sync</button>
</div>
<div id="fileImportTab" class="import-tab-content active">
<div class="form-group">
<label for="importFormat">Import Format:</label>
<select id="importFormat">
<option value="netscape">Netscape HTML</option>
<option value="chrome">Chrome JSON</option>
<option value="firefox">Firefox JSON</option>
<option value="auto">Auto-detect</option>
</select>
</div>
<div class="form-group">
<input type="file" id="fileInput" accept=".html,.htm,.json">
</div>
<div class="form-group">
<label for="importMode">Import Mode:</label>
<select id="importMode">
<option value="preview">Preview before import</option>
<option value="merge">Merge with existing</option>
<option value="replace">Replace all</option>
<option value="incremental">Incremental import</option>
</select>
</div>
<div class="duplicate-handling" id="duplicateHandling" style="display: none;">
<h3>Duplicate Detection:</h3>
<label><input type="checkbox" id="normalizeUrls" checked> Normalize URLs</label>
<label><input type="checkbox" id="fuzzyTitleMatch"> Fuzzy title matching</label>
<select id="duplicateStrategy">
<option value="skip">Skip duplicates</option>
<option value="update">Update existing</option>
<option value="keep_newer">Keep newer</option>
<option value="keep_older">Keep older</option>
</select>
</div>
</div>
<div id="syncImportTab" class="import-tab-content">
<div class="sync-section">
<h3>Device Synchronization</h3>
<select id="syncMethod">
<option value="cloud">Cloud Storage</option>
<option value="qr">QR Code Transfer</option>
<option value="local">Local Network</option>
</select>
<div id="cloudSyncOptions" class="sync-options">
<select id="cloudProvider">
<option value="gdrive">Google Drive</option>
<option value="dropbox">Dropbox</option>
</select>
<button id="connectCloudBtn" class="btn btn-secondary">Connect</button>
<div id="cloudStatus" class="cloud-status"></div>
</div>
<div id="qrSyncOptions" class="sync-options" style="display: none;">
<button id="generateQRBtn" class="btn btn-primary">Generate QR</button>
<div id="qrCodeDisplay" class="qr-display"></div>
<button id="scanQRBtn" class="btn btn-secondary">Scan QR</button>
<div id="qrScanArea" class="qr-scan-area"></div>
</div>
<div id="localSyncOptions" class="sync-options" style="display: none;">
<button id="startLocalServerBtn" class="btn btn-primary">Start Server</button>
<div id="localSyncStatus" class="sync-status"></div>
</div>
</div>
</div>
<div class="modal-actions">
<button id="importFileBtn" class="btn btn-primary">Import</button>
<button id="previewImportBtn" class="btn btn-secondary">Preview</button>
<button id="cancelImportBtn" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
<div id="importPreviewModal" class="modal">
<div class="modal-content import-preview-content">
<button class="close">&times;</button>
<h2>Import Preview</h2>
<div class="import-summary">
<div class="import-stats">
<div class="stat-item">
<span class="stat-number" id="previewTotalCount">0</span>
<span class="stat-label">Total</span>
</div>
<div class="stat-item">
<span class="stat-number" id="previewNewCount">0</span>
<span class="stat-label">New</span>
</div>
<div class="stat-item">
<span class="stat-number" id="previewDuplicateCount">0</span>
<span class="stat-label">Duplicates</span>
</div>
<div class="stat-item">
<span class="stat-number" id="previewFolderCount">0</span>
<span class="stat-label">Folders</span>
</div>
</div>
</div>
<div class="preview-tabs">
<button class="preview-tab active" data-tab="new">New Bookmarks</button>
<button class="preview-tab" data-tab="duplicates">Duplicates</button>
<button class="preview-tab" data-tab="folders">Folders</button>
</div>
<div class="preview-content">
<div id="newBookmarksPreview" class="preview-tab-content active">
<div id="newBookmarksList" class="preview-list"></div>
</div>
<div id="duplicatesPreview" class="preview-tab-content">
<div id="duplicatesList" class="preview-list"></div>
</div>
<div id="foldersPreview" class="preview-tab-content">
<div id="foldersList" class="preview-list"></div>
</div>
</div>
<div class="modal-actions">
<button id="confirmImportBtn" class="btn btn-primary">Confirm Import</button>
<button id="modifyImportBtn" class="btn btn-secondary">Modify Settings</button>
<button id="cancelPreviewBtn" class="btn btn-secondary">Cancel</button>
</div>
</div>
</div>
</div>
<script src="script.js"></script>
<script>
// Initialize the bookmark manager
const manager = new BookmarkManager();
// Test 1: Import Modal
document.getElementById('testImportModal').addEventListener('click', () => {
try {
manager.showModal('importModal');
document.getElementById('importModalResult').innerHTML =
'<div class="success">✅ Import modal opened successfully! Check if tabs and options are visible.</div>';
} catch (error) {
document.getElementById('importModalResult').innerHTML =
`<div class="error">❌ Failed to open import modal: ${error.message}</div>`;
}
});
// Test 2: Chrome Import
document.getElementById('testChromeImport').addEventListener('click', async () => {
const sampleData = {
"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"
}
]
}
]
}
}
};
try {
const bookmarks = manager.parseChromeBookmarks(JSON.stringify(sampleData));
document.getElementById('chromeImportResult').innerHTML = `
<div class="success">✅ Chrome import successful!</div>
<div class="info">Parsed ${bookmarks.length} bookmarks:
<ul>
${bookmarks.map(b => `<li><strong>${b.title}</strong> - ${b.url} (${b.folder || 'Root'})</li>`).join('')}
</ul>
</div>
`;
} catch (error) {
document.getElementById('chromeImportResult').innerHTML =
`<div class="error">❌ Chrome import failed: ${error.message}</div>`;
}
});
// Test 3: Firefox Import
document.getElementById('testFirefoxImport').addEventListener('click', async () => {
const sampleData = [
{
"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
}
]
}
]
}
];
try {
const bookmarks = manager.parseFirefoxBookmarks(JSON.stringify(sampleData));
document.getElementById('firefoxImportResult').innerHTML = `
<div class="success">✅ Firefox import successful!</div>
<div class="info">Parsed ${bookmarks.length} bookmarks:
<ul>
${bookmarks.map(b => `<li><strong>${b.title}</strong> - ${b.url} (${b.folder || 'Root'})</li>`).join('')}
</ul>
</div>
`;
} catch (error) {
document.getElementById('firefoxImportResult').innerHTML =
`<div class="error">❌ Firefox import failed: ${error.message}</div>`;
}
});
// Test 4: Import Preview
document.getElementById('testImportPreview').addEventListener('click', () => {
// Add some existing bookmarks for duplicate testing
manager.bookmarks = [
{
id: 'existing1',
title: 'Google Search',
url: 'https://www.google.com/',
folder: 'Search',
addDate: Date.now() - 1000000,
status: 'valid'
}
];
const importData = {
bookmarks: [
{
id: 'import1',
title: 'New Site',
url: 'https://example.com',
folder: 'Examples',
addDate: Date.now(),
status: 'unknown'
},
{
id: 'import2',
title: 'Google',
url: 'https://google.com',
folder: 'Search',
addDate: Date.now(),
status: 'unknown'
}
],
format: 'test',
originalCount: 2
};
try {
const analysis = manager.analyzeImportData(importData, 'merge');
document.getElementById('previewResult').innerHTML = `
<div class="success">✅ Import preview analysis successful!</div>
<div class="info">Analysis results:
<ul>
<li>Total bookmarks to import: ${importData.bookmarks.length}</li>
<li>New bookmarks: ${analysis.newBookmarks.length}</li>
<li>Duplicates found: ${analysis.duplicates.length}</li>
<li>Folders: ${analysis.folders.length}</li>
</ul>
${analysis.duplicates.length > 0 ?
`<strong>Duplicates:</strong><ul>${analysis.duplicates.map(d =>
`<li>${d.imported.title} (${d.reason})</li>`
).join('')}</ul>` : ''}
</div>
`;
} catch (error) {
document.getElementById('previewResult').innerHTML =
`<div class="error">❌ Import preview failed: ${error.message}</div>`;
}
});
// Test 5: Incremental Import
document.getElementById('testIncrementalImport').addEventListener('click', async () => {
const originalCount = manager.bookmarks.length;
const importData = {
bookmarks: [
{
id: 'incremental1',
title: 'Stack Overflow',
url: 'https://stackoverflow.com',
folder: 'Development',
addDate: Date.now(),
lastModified: Date.now(),
status: 'unknown'
}
],
format: 'test',
originalCount: 1
};
try {
await manager.performAdvancedImport(importData, 'incremental', 'keep_newer');
const newCount = manager.bookmarks.length;
document.getElementById('incrementalResult').innerHTML = `
<div class="success">✅ Incremental import successful!</div>
<div class="info">
<ul>
<li>Bookmarks before: ${originalCount}</li>
<li>Bookmarks after: ${newCount}</li>
<li>Added: ${newCount - originalCount} bookmarks</li>
</ul>
</div>
`;
} catch (error) {
document.getElementById('incrementalResult').innerHTML =
`<div class="error">❌ Incremental import failed: ${error.message}</div>`;
}
});
// Test 6: Sync Features
document.getElementById('testSyncFeatures').addEventListener('click', () => {
try {
const deviceId = manager.getDeviceId();
const syncData = manager.exportForSync();
const hash = manager.calculateDataHash();
const testData = { test: 'sync' };
const compressed = manager.compressAndEncodeData(testData);
const decompressed = manager.decodeAndDecompressData(compressed);
document.getElementById('syncResult').innerHTML = `
<div class="success">✅ Sync features working!</div>
<div class="info">Test results:
<ul>
<li>Device ID: ${deviceId}</li>
<li>Sync data exported: ${syncData.bookmarks.length} bookmarks</li>
<li>Data hash: ${hash}</li>
<li>Compression test: ${decompressed.test === 'sync' ? 'Passed' : 'Failed'}</li>
<li>Sync metadata: Version ${syncData.version}, ${syncData.metadata.totalBookmarks} bookmarks</li>
</ul>
</div>
`;
} catch (error) {
document.getElementById('syncResult').innerHTML =
`<div class="error">❌ Sync features failed: ${error.message}</div>`;
}
});
// Show current bookmarks
document.getElementById('showBookmarks').addEventListener('click', () => {
const bookmarks = manager.bookmarks;
if (bookmarks.length === 0) {
document.getElementById('bookmarksDisplay').innerHTML =
'<div class="info">No bookmarks currently loaded.</div>';
} else {
const folderStats = {};
bookmarks.forEach(b => {
const folder = b.folder || 'Uncategorized';
folderStats[folder] = (folderStats[folder] || 0) + 1;
});
document.getElementById('bookmarksDisplay').innerHTML = `
<div class="info">
<strong>Current bookmarks: ${bookmarks.length}</strong>
<br><strong>Folders:</strong>
<ul>
${Object.entries(folderStats).map(([folder, count]) =>
`<li>${folder}: ${count} bookmark${count > 1 ? 's' : ''}</li>`
).join('')}
</ul>
<strong>Recent bookmarks:</strong>
<ul>
${bookmarks.slice(-5).map(b =>
`<li><strong>${b.title}</strong> - ${b.url} (${b.folder || 'Root'})</li>`
).join('')}
</ul>
</div>
`;
}
});
// Auto-run some tests on page load
setTimeout(() => {
document.getElementById('showBookmarks').click();
}, 1000);
</script>
</body>
</html>