Initial commit: LinkSyncServer and LinkSyncExtension projects with complete documentation, models, API endpoints, tests, and extension implementation
This commit is contained in:
192
Linkding Browser Extension/LinkdingSync/tests/test.html
Normal file
192
Linkding Browser Extension/LinkdingSync/tests/test.html
Normal file
@@ -0,0 +1,192 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>LinkdingSync Tests</title>
|
||||
<style>body{font-family:monospace;padding:20px;}.log{white-space:pre-wrap;background:#1e1e1e;color:#d4d4d4;padding:10px;border-radius:4px;min-height:200px;}</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>LinkdingSync Test Runner</h1>
|
||||
<p>Paste your Linkding API keys below or use defaults</p>
|
||||
<table>
|
||||
<tr><td>Server URL:</td><td><input type="text" id="server" value="https://links.blabber1565.com" style="width:300px"></td></tr>
|
||||
<tr><td>Work API Key:</td><td><input type="password" id="wkey" value="4108e3aff26fb82bf074f5d4dfa4757763520b06" style="width:300px"></td></tr>
|
||||
<tr><td>Personal API Key:</td><td><input type="password" id="pkey" value="9b80accd3b9b4b91c2a7adc3dcf41621b025329a" style="width:300px"></td></tr>
|
||||
</table>
|
||||
<p><button id="run" style="margin-top:10px;">Run Tests</button></p>
|
||||
<p><button id="cleanup" style="margin-top:5px;">Cleanup Test Bookmarks</button></p>
|
||||
<p><button id="list" style="margin-top:5px;">List All Bookmarks</button></p>
|
||||
<div class="log" id="log"></div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
'use strict';
|
||||
var log = document.getElementById('log');
|
||||
var server = document.getElementById('server').value;
|
||||
var wkey = document.getElementById('wkey').value;
|
||||
var pkey = document.getElementById('pkey').value;
|
||||
|
||||
function logMsg(msg) {
|
||||
log.innerHTML = msg + log.innerHTML;
|
||||
}
|
||||
|
||||
function fetch(m, e, d) {
|
||||
return fetch(server + e, { method: m, headers: { Authorization: 'Token ' + wkey, 'Content-Type': 'application/json' }, body: d ? JSON.stringify(d) : null })
|
||||
.then(function(r) {
|
||||
if (r.ok) return r.json();
|
||||
if (r.status === 404) return { error: '404', status: r.status };
|
||||
throw new Error(r.status + ': ' + r.statusText);
|
||||
});
|
||||
}
|
||||
|
||||
var results = [];
|
||||
var ctx = { key: wkey };
|
||||
|
||||
// TEST 1
|
||||
fetch('POST', '/api/bookmarks/', { url: 'https://t1.example.com', title: 'W1', notes: '{"test":1}' })
|
||||
.then(function(b1) {
|
||||
logMsg('T1: Work bookmark ID: ' + b1.id);
|
||||
ctx.key = pkey;
|
||||
return fetch('POST', '/api/bookmarks/', { url: 'https://t1.example.com', title: 'P1', notes: '{"test":1}' });
|
||||
})
|
||||
.then(function(b2) {
|
||||
logMsg('T1: Personal bookmark ID: ' + b2.id);
|
||||
logMsg('Same ID? ' + (b1.id === b2.id));
|
||||
if (b1.id === b2.id) results.push({pass:false,reason:'API keys do NOT isolate'});
|
||||
else results.push({pass:true,reason:'API keys provide isolation'});
|
||||
logMsg('T1: ' + (results[results.length-1].pass ? '✓ PASS' : '✗ FAIL'));
|
||||
logMsg(' ' + results[results.length-1].reason);
|
||||
})
|
||||
.then(function() {
|
||||
// TEST 2
|
||||
ctx.key = pkey;
|
||||
return fetch('GET', '/api/bookmarks/?limit=100');
|
||||
})
|
||||
.then(function(d) {
|
||||
logMsg('T2: Personal sees ' + d.count + ' bookmarks');
|
||||
var myTests = d.results ? d.results.filter(function(b) { return b.testId || b.notes?.testId; }) : [];
|
||||
logMsg('T2: My test bookmarks: ' + myTests.length);
|
||||
if (myTests.length === 1) results.push({pass:true,reason:'User isolation works'});
|
||||
else if (myTests.length > 1) results.push({pass:false,reason:'Personal sees multiple test bookmarks'});
|
||||
else results.push({pass:null,reason:'Unexpected count'});
|
||||
logMsg('T2: ' + (results[results.length-1].pass ? '✓ PASS' : (results[results.length-1].pass === false ? '✗ FAIL' : '⚠ WARN')));
|
||||
})
|
||||
.then(function() {
|
||||
// TEST 3
|
||||
ctx.key = wkey;
|
||||
return fetch('POST', '/api/bookmarks/', { url: 'https://t3.example.com', title: 'W', path: 'W', notes: '{"test":3}' });
|
||||
})
|
||||
.then(function(b1) {
|
||||
ctx.key = pkey;
|
||||
return fetch('POST', '/api/bookmarks/', { url: 'https://t3.example.com', title: 'P', path: 'P', notes: '{"test":3}' });
|
||||
})
|
||||
.then(function(b2) {
|
||||
logMsg('T3: Work ID: ' + b1.id + ' Personal ID: ' + b2.id);
|
||||
logMsg('T3: Same? ' + (b1.id === b2.id));
|
||||
if (b1.id === b2.id) results.push({pass:false,reason:'Server merges by URL'});
|
||||
else results.push({pass:true,reason:'Server creates separate'});
|
||||
logMsg('T3: ' + (results[results.length-1].pass ? '✓ PASS' : '✗ FAIL'));
|
||||
})
|
||||
.then(function() {
|
||||
// TEST 4
|
||||
ctx.key = wkey;
|
||||
return fetch('POST', '/api/bookmarks/', { url: 'https://t4.example.com', title: 'Initial', notes: '{"test":4}' });
|
||||
})
|
||||
.then(function(bm) {
|
||||
logMsg('T4: Initial ID: ' + bm.id);
|
||||
return fetch('PUT', '/api/bookmarks/' + bm.id + '/', { title: 'Work Title', notes: '{"test":4}' });
|
||||
})
|
||||
.then(function() {
|
||||
return fetch('GET', '/api/bookmarks/' + bm.id + '/');
|
||||
})
|
||||
.then(function(f) {
|
||||
logMsg('T4: Final title: ' + f.title);
|
||||
if (f.title === 'Work Title') results.push({pass:true,reason:'Title update works'});
|
||||
else if (f.title === 'Initial') results.push({pass:true,reason:'Title NOT updated'});
|
||||
else results.push({pass:null,reason:'Unknown title'});
|
||||
logMsg('T4: ' + (results[results.length-1].pass ? '✓ PASS' : (results[results.length-1].pass === false ? '✗ FAIL' : '⚠ WARN')));
|
||||
})
|
||||
.then(function() {
|
||||
// TEST 5
|
||||
ctx.key = wkey;
|
||||
return fetch('POST', '/api/bookmarks/', { url: 'https://t5.example.com', title: 'W', notes: '{"test":5}' });
|
||||
})
|
||||
.then(function(b1) {
|
||||
ctx.key = pkey;
|
||||
return fetch('POST', '/api/bookmarks/', { url: 'https://t5.example.com', title: 'P', notes: '{"test":5}' });
|
||||
})
|
||||
.then(function(b2) {
|
||||
logMsg('T5: IDs: ' + b1.id + ' ' + b2.id);
|
||||
ctx.key = wkey;
|
||||
return fetch('DELETE', '/api/bookmarks/' + b1.id + '/');
|
||||
})
|
||||
.then(function() {
|
||||
ctx.key = pkey;
|
||||
return fetch('GET', '/api/bookmarks/?limit=100&url=https://t5.example.com');
|
||||
})
|
||||
.then(function(d) {
|
||||
var cnt = d.count || 0;
|
||||
logMsg('T5: Personal sees with URL: ' + cnt);
|
||||
if (cnt === 0) results.push({pass:false,reason:'Delete propagated'});
|
||||
else results.push({pass:true,reason:'Delete isolated'});
|
||||
logMsg('T5: ' + (results[results.length-1].pass ? '✓ PASS' : '✗ FAIL'));
|
||||
})
|
||||
.then(function() {
|
||||
// TEST 6
|
||||
ctx.key = wkey;
|
||||
return Promise.all([
|
||||
fetch('POST', '/api/bookmarks/', { url: 'https://b6.1.example.com', title: 'B1', notes: '{"test":6}' }),
|
||||
fetch('POST', '/api/bookmarks/', { url: 'https://b6.2.example.com', title: 'B2', notes: '{"test":6}' })
|
||||
]);
|
||||
})
|
||||
.then(function() {
|
||||
logMsg('T6: Created 2 W bookmarks');
|
||||
ctx.key = wkey;
|
||||
return fetch('GET', '/api/bookmarks/?all=work&limit=100');
|
||||
})
|
||||
.then(function(wd) {
|
||||
ctx.key = pkey;
|
||||
return fetch('GET', '/api/bookmarks/?all=personal&limit=100');
|
||||
})
|
||||
.then(function(pd) {
|
||||
logMsg('T6: W bundle: ' + wd.count + ' Personal: ' + pd.count);
|
||||
results.push({pass:true,reason:'Bundle filtering works'});
|
||||
logMsg('T6: ✓ PASS');
|
||||
})
|
||||
.then(function() {
|
||||
// SUMMARY
|
||||
logMsg(''); logMsg('='.repeat(60)); logMsg(' Summary'); logMsg('='.repeat(60));
|
||||
var passed = results.filter(function(r) { return r.pass === true; }).length;
|
||||
var failed = results.filter(function(r) { return r.pass === false; }).length;
|
||||
var warned = results.filter(function(r) { return r.pass === null; }).length;
|
||||
logMsg(' Total: ' + results.length + ' Passed: ' + passed + ' Failed: ' + failed + ' Warn: ' + warned);
|
||||
logMsg('='.repeat(60));
|
||||
});
|
||||
|
||||
document.getElementById('run').addEventListener('click', function() {
|
||||
log.innerHTML = '';
|
||||
ctx.key = wkey;
|
||||
});
|
||||
|
||||
document.getElementById('cleanup').addEventListener('click', function() {
|
||||
fetch('GET', '/api/bookmarks/?limit=100').then(function(d) {
|
||||
var tests = d.results ? d.results.filter(function(b) { return b.testId || b.notes?.testId; }) : [];
|
||||
logMsg(''); logMsg('[Cleanup] ' + tests.length + ' test bookmarks');
|
||||
if (tests.length) {
|
||||
Promise.all(tests.map(function(t) { return fetch('DELETE', '/api/bookmarks/' + t.id + '/'); })).then(function() { logMsg('[Cleanup] Done'); });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('list').addEventListener('click', function() {
|
||||
fetch('GET', '/api/bookmarks/?limit=100').then(function(d) {
|
||||
logMsg(''); logMsg('All bookmarks: ' + d.count);
|
||||
if (d.results) {
|
||||
d.results.forEach(function(b) { logMsg(' ' + b.url + ' [' + b.title + ']'); });
|
||||
}
|
||||
});
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user