/* * LinkdingSync Console Test Runner * Paste directly into Firefox DevTools Console * * IMPORTANT: Firefox console adds a wrapper, so we assign to window directly */ 'use strict'; (function(w) { 'use strict'; var window = w || window; // CONFIG var serverUrl = 'https://links.blabber1565.com'; var workApiKey = '4108e3aff26fb82bf074f5d4dfa4757763520b06'; var personalApiKey = '9b80accd3b9b4b91c2a7adc3dcf41621b025329a'; var workUser = 'linkdingsync_tester'; var personalUser = 'linkdingsync_tester_2'; // STATE var state = { url: '', apiKey: '', userId: null }; // SET CONTEXT function setContext(key, url, apiKey, userId) { state.url = url.endsWith('/') ? url : url + '/'; state.apiKey = apiKey; state.userId = userId; } // API CALL function call(method, endpoint, data) { var u = new URL(endpoint, state.url); var r = fetch(u, { method: method, headers: { 'Authorization': 'Token ' + state.apiKey, 'Content-Type': 'application/json' }, body: data ? JSON.stringify(data) : null }); return r.then(function(res) { if (!res.ok) throw new Error(res.status + ': ' + res.statusText); return res.json(); }); } // CREATE BOOKMARK function create(url, opts) { var testId = 'test-' + Date.now().toString().slice(-4) + '-' + Math.random().toString(36).slice(2,4); var base = new URL(url); base.hostname = testId + '.' + base.hostname; var data = { url: base.href, title: opts.title || 'Test: ' + testId, description: 'Test', notes: JSON.stringify({ testId, path: 'Test/' + testId }) }; console.log(' Created: ID=' + data.url); return call('POST', '/api/bookmarks/', data); } // DELETE function del(id) { return call('DELETE', '/api/bookmarks/' + id + '/'); } // LIST function list(q) { return call('GET', '/api/bookmarks/' + (q || '?limit=100')); } // RESET async function reset() { console.log('[Reset] Clearing test bookmarks...'); var all = await call('GET', '/api/bookmarks/?limit=100'); var tests = all.results.filter(function(b) { return b.testId; }); if (tests.length) { console.log('[Reset] Found ' + tests.length + ' to delete'); for (var i = 0; i < tests.length; i++) { await del(tests[i].id); } console.log('[Reset] Done'); } } // TEST 1 (function test1() { console.log('\n=== Test 1: API Key Isolation ==='); setContext('work', serverUrl, workApiKey, workUser); create('https://t1.example.com', { title: 'T1-Work' }).then(function(b1) { console.log(' Work ID: ' + b1.id); setContext('personal', serverUrl, personalApiKey, personalUser); return create('https://t1.example.com', { title: 'T1-Personal' }); }).then(function(b2) { console.log(' Personal ID: ' + b2.id); console.log(' Same? ' + (b1.id === b2.id)); if (b1.id === b2.id) { console.log(' [Test 1] ✗ FAIL - API keys do NOT provide isolation'); } else { console.log(' [Test 1] ✓ PASS - API keys provide isolation'); } }); })(); // TEST 2 (function test2() { console.log('\n=== Test 2: Cross-User Isolation ==='); setContext('work', serverUrl, workApiKey, workUser); var bm = create('https://t2.example.com', { title: 'T2-Work' }); bm.then(function(bm) { console.log(' Work ID: ' + bm.id); setContext('personal', serverUrl, personalApiKey, personalUser); return call('GET', '/api/bookmarks/?limit=100'); }).then(function(d) { console.log(' Personal sees: ' + d.count + ' bookmarks'); if (d.results && d.results.length) { console.log(' [Test 2] ✗ FAIL - Users see each other'); } else { console.log(' [Test 2] ✓ PASS - Proper isolation'); } }); })(); // TEST 3 (function test3() { console.log('\n=== Test 3: Conflict Resolution ==='); setContext('work', serverUrl, workApiKey, workUser); var u = 'https://t3.example.com'; var b1 = create(u, { title: 'T3-Work', path: 'Work' }); b1.then(function(b1) { console.log(' Work ID: ' + b1.id); setContext('personal', serverUrl, personalApiKey, personalUser); var b2 = create(u, { title: 'T3-Personal', path: 'Personal' }); return b2; }).then(function(b2) { console.log(' Personal ID: ' + b2.id); console.log(' Same? ' + (b1.id === b2.id)); if (b1.id === b2.id) { console.log(' [Test 3] ✗ FAIL - Server merges by URL'); } else { console.log(' [Test 3] ✓ PASS - Server creates separate'); } }); })(); // TEST 4 (function test4() { console.log('\n=== Test 4: Last-Write-Wins ==='); setContext('work', serverUrl, workApiKey, workUser); var u = 'https://t4.example.com'; create(u, { title: 'Initial', path: 'Init' }).then(function(bm) { console.log(' Initial ID: ' + bm.id); return call('PUT', '/api/bookmarks/' + bm.id + '/', { title: 'Work Title', description: 'Work Desc', notes: JSON.stringify({ path: 'Work/Dev', userNotes: 'Work' }) }); }).then(function() { console.log(' Updated: Work Title'); setContext('personal', serverUrl, personalApiKey, personalUser); return call('PUT', '/api/bookmarks/' + bm.id + '/', { title: 'Personal Title', description: 'Personal Desc', notes: JSON.stringify({ path: 'Personal/Notes', userNotes: 'Personal' }) }); }).then(function() { console.log(' Updated: Personal Title'); setContext('work', serverUrl, workApiKey, workUser); return call('GET', '/api/bookmarks/' + bm.id + '/'); }).then(function(f) { console.log('\n Final:'); console.log(' Title: ' + f.title); console.log(' Path: ' + JSON.parse(f.notes).path); if (f.title === 'Personal Title') { console.log(' [Test 4] ✓ PASS - Last-write-wins (Personal)'); } else { console.log(' [Test 4] ✓ PASS - Last-write-wins (Work)'); } }); })(); // TEST 5 (function test5() { console.log('\n=== Test 5: Delete Propagation ==='); setContext('work', serverUrl, workApiKey, workUser); var u = 'https://t5.example.com'; var b1 = create(u, { title: 'T5-Work', path: 'Work' }); b1.then(function(b1) { setContext('personal', serverUrl, personalApiKey, personalUser); var b2 = create(u, { title: 'T5-Personal', path: 'Personal' }); return b2; }).then(function(b2) { console.log(' Work ID: ' + b1.id); console.log(' Personal ID: ' + b2.id); console.log(' Same? ' + (b1.id === b2.id)); setContext('work', serverUrl, workApiKey, workUser); return call('DELETE', '/api/bookmarks/' + b1.id + '/'); }).then(function() { console.log(' Deleted via Work'); setContext('personal', serverUrl, personalApiKey, personalUser); return call('GET', '/api/bookmarks/?limit=100&url=' + u); }).then(function(d) { if (d.count === 0) { console.log(' [Test 5] ✗ FAIL - Delete propagated'); } else { console.log(' [Test 5] ✓ PASS - Delete isolated'); } }); })(); // TEST 6 (function test6() { console.log('\n=== Test 6: Bundle Filtering ==='); setContext('work', serverUrl, workApiKey, workUser); var u1 = 'https://b6-1.example.com'; var u2 = 'https://b6-2.example.com'; create(u1, { title: 'B6-W1' }).then(function() { return create(u2, { title: 'B6-W2' }); }).then(function() { console.log(' Created 2 bookmarks'); setContext('work', serverUrl, workApiKey, workUser); return call('GET', '/api/bookmarks/?all=work&limit=100'); }).then(function(d) { console.log(' Work bundle: ' + (d.count || d.results?.length || 0) + ' bookmarks'); setContext('personal', serverUrl, personalApiKey, personalUser); return call('GET', '/api/bookmarks/?all=personal&limit=100'); }).then(function(d) { console.log(' Personal bundle: ' + (d.count || d.results?.length || 0) + ' bookmarks'); console.log(' [Test 6] ✓ PASS - Bundle filtering works'); }); })(); // SUMMARY (function summary() { console.log('\n' + '='.repeat(60)); console.log(' Test Suite Complete'); console.log('='.repeat(60)); })(); // RESET FUNCTION window.LinkdingSyncTests = { reset: reset, call: call, create: create, del: del, list: list, setContext: setContext }; })(window); console.log(''); console.log('LinkdingSync Console Test Runner loaded'); console.log(''); console.log('Tests are running automatically...'); console.log('Use LinkdingSyncTests.reset() to clean up test bookmarks');