Initial commit: LinkSyncServer and LinkSyncExtension projects with complete documentation, models, API endpoints, tests, and extension implementation
This commit is contained in:
196
Linkding Browser Extension/LinkdingSync/tests/test-conflicts.js
Normal file
196
Linkding Browser Extension/LinkdingSync/tests/test-conflicts.js
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* 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
|
||||
};
|
||||
Reference in New Issue
Block a user