/* * Test Module: Conflict Resolution * Tests scenarios 3 and 4 */ 'use strict'; const utils = require('../utils.js').LinkdingSyncTests; const SCENARIO_NAME = 'Conflict Resolution Tests'; // Test 3: Conflict Resolution - Different Paths async function test3_ConflictResolution() { console.log('\n=== Test 3: Conflict Resolution - Different Paths ==='); console.log('Purpose: How server handles same URL in different paths with different API keys'); try { // Create with work API key utils.SessionManager.setContext( CONFIG.serverUrl, CONFIG.workApiKey, CONFIG.workUser, CONFIG.workBundle ); const workUrl = 'https://conflict-resolution.example.com'; const workBookmark = await utils.Helpers.createBookmark(workUrl, { title: 'Conflict Resolution Test', path: 'Work/Development', notes: 'Work Development Notes' }); console.log(` Work bookmark ID: ${workBookmark.id}`); // Create same URL with personal API key utils.SessionManager.setContext( CONFIG.serverUrl, CONFIG.personalApiKey, CONFIG.personalUser, CONFIG.personalBundle ); const personalBookmark = await utils.Helpers.createBookmark(workUrl, { title: 'Conflict Resolution Test', path: 'Personal/Notes', notes: 'Personal Notes' }); console.log(` Personal bookmark ID: ${personalBookmark.id}`); // Compare console.log(`\nComparing bookmark IDs: work=${workBookmark.id}, personal=${personalBookmark.id}`); if (workBookmark.id === personalBookmark.id) { utils.Formatters.consoleResult('Test 3', 'FAIL', 'Same bookmark ID'); console.log(' → Server merges bookmarks by URL'); console.log(' → Need path merge strategy'); const state = await utils.Helpers.fetchBookmark(workBookmark.id); const parsed = utils.Helpers.parseNotes(state.notes); console.log(` → Current path: ${parsed.path}`); console.log(` → Current notes: ${parsed.userNotes}`); return { pass: false, sameId: true, path: parsed.path }; } else { utils.Formatters.consoleResult('Test 3', 'PASS', 'Different bookmark IDs'); console.log(' → Server creates separate bookmarks per API key'); console.log(' → Can use different API keys for isolation'); return { pass: true, sameId: false, workId: workBookmark.id, personalId: personalBookmark.id }; } } catch (error) { utils.Formatters.consoleResult('Test 3', 'FAIL', error.message); throw error; } } // Test 4: Title/Description Conflict async function test4_TitleDescriptionConflict() { console.log('\n=== Test 4: Title/Description Conflict ==='); console.log('Purpose: How server resolves conflicts for title/description fields'); try { utils.SessionManager.setContext( CONFIG.serverUrl, CONFIG.workApiKey, CONFIG.workUser, CONFIG.workBundle ); const testUrl = 'https://title-conflict.example.com'; // Create initial const bookmark = await utils.Helpers.createBookmark(testUrl, { title: 'Initial Title', description: 'Initial Description', path: 'Initial' }); // Update via work await utils.Helpers.updateBookmark(bookmark.id, { title: 'Work Title', description: 'Work Description', notes: JSON.stringify({ path: 'Work/Dev', userNotes: 'Work notes', autoTags: [{name: 'Work'}] }) }); console.log(' Updated via Work: Work Title'); // Update via personal utils.SessionManager.setContext( CONFIG.serverUrl, CONFIG.personalApiKey, CONFIG.personalUser, CONFIG.personalBundle ); await utils.Helpers.updateBookmark(bookmark.id, { title: 'Personal Title', description: 'Personal Description', notes: JSON.stringify({ path: 'Personal/Notes', userNotes: 'Personal notes', autoTags: [{name: 'Personal'}] }) }); console.log(' Updated via Personal: Personal Title'); // Fetch final state const final = await utils.Helpers.fetchBookmark(bookmark.id); const parsed = utils.Helpers.parseNotes(final.notes); console.log('\nFinal state:'); console.log(` Title: ${final.title}`); console.log(` Description: ${final.description}`); console.log(` Path: ${parsed.path}`); console.log(` User notes: ${parsed.userNotes}`); if (final.title === 'Personal Title') { utils.Formatters.consoleResult('Test 4', 'PASS', 'Last-write-wins (Personal took precedence)'); return { pass: true, strategy: 'last-write-wins', winner: 'personal' }; } else if (final.title === 'Work Title') { utils.Formatters.consoleResult('Test 4', 'PASS', 'Last-write-wins (Work took precedence)'); return { pass: true, strategy: 'last-write-wins', winner: 'work' }; } else if (final.title.includes('Work') && final.title.includes('Personal')) { utils.Formatters.consoleResult('Test 4', 'PASS', 'Merged title'); return { pass: true, strategy: 'merge', winner: 'merged' }; } else { utils.Formatters.consoleResult('Test 4', 'WARN', 'Unexpected title value'); return { pass: null, strategy: 'unknown', winner: final.title }; } } catch (error) { utils.Formatters.consoleResult('Test 4', 'FAIL', error.message); throw error; } } // Run all tests async function runConflictTests() { console.log('\n' + '='.repeat(60)); console.log(' ' + SCENARIO_NAME); console.log('='.repeat(60)); const results = []; try { results[0] = await test3_ConflictResolution(); results[1] = await test4_TitleDescriptionConflict(); } catch (error) { console.error('Test suite error:', error.message); utils.Helpers.resetBookmarks(); } console.log('\n' + '='.repeat(60)); console.log(' Conflict Resolution Tests Complete'); console.log('='.repeat(60)); return results; } // Export window.LinkdingSyncTests.TestConflicts = { run: runConflictTests, test3: test3_ConflictResolution, test4: test4_TitleDescriptionConflict };