/* * LinkdingSync Test Utilities * Shared functions for test modules */ 'use strict'; // ==================================================================== // CONFIGURATION // ==================================================================== const CONFIG = { serverUrl: 'https://links.blabber1565.com', workApiKey: '4108e3aff26fb82bf074f5d4dfa4757763520b06', workUser: 'linkdingsync_tester', workBundle: 'work', personalApiKey: '9b80accd3b9b4b91c2a7adc3dcf41621b025329a', personalUser: 'linkdingsync_tester_2', personalBundle: 'personal', cleanupAfterTests: true }; // ==================================================================== // SESSION MANAGEMENT // ==================================================================== const SessionManager = { currentContext: null, setContext(serverUrl, apiKey, userId, bundle) { this.currentContext = { serverUrl: serverUrl.endsWith('/') ? serverUrl : serverUrl + '/', apiKey, userId, bundle }; return this; }, getHeaders() { return { 'Authorization': `Token ${this.currentContext.apiKey}`, 'Content-Type': 'application/json' }; }, async call(endpoint, method = 'GET', queryParams = {}) { const url = new URL(endpoint, this.currentContext.serverUrl); Object.entries(queryParams).forEach(([key, value]) => { url.searchParams.append(key, value); }); const response = await fetch(url, { method, headers: this.getHeaders(), body: null }); if (!response.ok) { const text = await response.text().slice(0, 200); throw new Error(`${response.status}: ${response.statusText} - ${text}`); } return await response.json(); } }; // ==================================================================== // HELPERS // ==================================================================== const Helpers = { generateTestId(prefix = 'test') { return `${prefix}-${Date.now().toString().slice(-4)}-${Math.random().toString(36).substring(2, 4)}`; }, async createBookmark(url, options = {}) { const testId = this.generateTestId(); const baseUrl = new URL(url); baseUrl.hostname = `${testId}.${baseUrl.hostname}`; const bookmarkData = { url: baseUrl.href, title: options.title || `Test: ${testId}`, description: options.description || 'Test bookmark', notes: JSON.stringify({ path: options.path || `Test/${testId}`, userNotes: options.notes || 'Test bookmark', testId }) }; const response = await SessionManager.call('/api/bookmarks/', 'POST', null, {}); console.log(` Created: ID=${response.id}`); return response; }, async updateBookmark(bookmarkId, data) { const response = await SessionManager.call(`/api/bookmarks/${bookmarkId}/`, 'PUT', null, {}); console.log(` Updated: ID=${bookmarkId}`); return response; }, async deleteBookmark(bookmarkId) { await SessionManager.call(`/api/bookmarks/${bookmarkId}/`, 'DELETE', {}); console.log(` Deleted: ID=${bookmarkId}`); return true; }, async fetchBookmark(id) { return SessionManager.call(`/api/bookmarks/${id}/`); }, parseNotes(noteString) { if (!noteString) return null; try { const parsed = JSON.parse(noteString); return parsed; } catch { return { userNotes: noteString, version: '1.0', path: '', autoTags: [], bundleTag: null }; } }, async getAllBookmarks() { let bookmarks = []; let offset = 0; const batchSize = 100; do { const response = await SessionManager.call('/api/bookmarks/', 'GET', { limit: batchSize, offset }); bookmarks.push(...(response.results || [])); offset += batchSize; } while (bookmarks.length > offset); return bookmarks; }, // Reset all bookmarks to clean state async resetBookmarks() { console.log('[Utils] Resetting all bookmarks...'); try { const allBookmarks = await this.getAllBookmarks(); const testBookmarks = allBookmarks.filter(b => b.testId); if (testBookmarks.length > 0) { console.log(`[Utils] Found ${testBookmarks.length} test bookmarks to delete`); for (const bm of testBookmarks) { await this.deleteBookmark(bm.id); } console.log('[Utils] Reset complete'); } else { console.log('[Utils] No test bookmarks found'); } } catch (error) { console.error('[Utils] Reset failed:', error.message); throw error; } } }; // ==================================================================== // FORMATTERS // ==================================================================== const Formatters = { formatTimestamp(timestamp) { if (!timestamp) return 'Never'; return new Date(timestamp).toLocaleString('en-US', { year: 'numeric', month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }); }, consoleHeader(text) { console.log(''.padEnd(60, '=')); console.log(` ${text}`.padEnd(60, '='.charCodeAt(0) === text.charCodeAt(0) ? '=' : '-').padEnd(60, '=')); console.log(''.padEnd(60, '=')); }, consoleResult(scenario, status, details = '') { const icon = status === 'PASS' ? '✓' : status === 'FAIL' ? '✗' : '⚠'; const emoji = status === 'PASS' ? '✅' : status === 'FAIL' ? '❌' : '⚠️'; console.log(` [${scenario}] ${icon} ${emoji} ${status}`); if (details) console.log(` ${details}`); } }; // ==================================================================== // EXPORT // ==================================================================== window.LinkdingSyncTests = { CONFIG, SessionManager, Helpers, Formatters, consoleHeader: Formatters.consoleHeader, consoleResult: Formatters.consoleResult };