194 lines
10 KiB
JavaScript
194 lines
10 KiB
JavaScript
/*
|
|
* LinkdingSync Final Test Runner
|
|
* Firefox Console Compatible
|
|
*/
|
|
|
|
(function(w, eval) {
|
|
'use strict';
|
|
var window = w || window;
|
|
var E = eval;
|
|
|
|
// === CONFIG ===
|
|
var SERVER_URL = 'https://links.blabber1565.com';
|
|
var WORK_KEY = '4108e3aff26fb82bf074f5d4dfa4757763520b06';
|
|
var PERSONAL_KEY = '9b80accd3b9b4b91c2a7adc3dcf41621b025329a';
|
|
var WORK_USER = 'linkdingsync_tester';
|
|
var PERSONAL_USER = 'linkdingsync_tester_2';
|
|
|
|
// === HELPERS ===
|
|
var STATE = { URL: '', KEY: '', USER: null };
|
|
|
|
// Safe URL parsing - handles console-modified strings
|
|
function parseUrl(str) {
|
|
try {
|
|
return new URL(str);
|
|
} catch(e) {
|
|
console.log(' [WARN] Invalid URL: ' + str);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function API(method, endpoint, data) {
|
|
var url = parseUrl(STATE.URL + endpoint);
|
|
if (!url) throw new Error('Invalid base URL');
|
|
var r = E(function(res) {
|
|
if (!res.ok && res.status === 404) return { error: '404', status: res.status };
|
|
if (!res.ok) throw new Error(res.status + ': ' + res.statusText);
|
|
return res.json();
|
|
})(url, { method: method, headers: { 'Authorization': 'Token ' + STATE.KEY, 'Content-Type': 'application/json' }, body: data ? JSON.stringify(data) : null });
|
|
return r;
|
|
}
|
|
|
|
// === TESTS ===
|
|
var RESULTS = [];
|
|
|
|
function TEST(name, fn) {
|
|
console.log(''); console.log('=== ' + name + ' ===');
|
|
var promise = E(function() { return fn(); });
|
|
promise.then(function(r) {
|
|
console.log(' [PASS/FAIL/' + (r.pass === null ? 'WARN' : 'PASS') + '] ' + r.reason);
|
|
RESULTS.push(r);
|
|
return r;
|
|
}).catch(function(e) {
|
|
console.log(' [ERROR] ' + e.message);
|
|
RESULTS.push({ pass: false, reason: 'Error: ' + e.message });
|
|
return { pass: false, reason: 'Error: ' + e.message };
|
|
});
|
|
return promise;
|
|
}
|
|
|
|
// === MAIN ===
|
|
TEST('API Key Isolation', function() {
|
|
STATE.URL = SERVER_URL; STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t1.example.com', title: 'T1-Work', description: 'Test', notes: JSON.stringify({ testId: true }) })
|
|
.then(function(b1) {
|
|
STATE.KEY = PERSONAL_KEY; STATE.USER = PERSONAL_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t1.example.com', title: 'T1-Personal', description: 'Test', notes: JSON.stringify({ testId: true }) });
|
|
})
|
|
.then(function(b2) {
|
|
console.log(' IDs: ' + b1.id + ' vs ' + b2.id + ' Same? ' + (b1.id === b2.id));
|
|
if (b1.id === b2.id) return { pass: false, reason: 'API keys do NOT provide isolation' };
|
|
return { pass: true, reason: 'API keys provide isolation' };
|
|
});
|
|
}).then(function(r1) {
|
|
console.log(''); console.log('=== Cross-User Visibility ===');
|
|
STATE.KEY = PERSONAL_KEY; STATE.USER = PERSONAL_USER;
|
|
return API('GET', '/api/bookmarks/?limit=100').then(function(data) {
|
|
var myTests = data.results ? data.results.filter(function(b) { return b.testId; }) : [];
|
|
console.log(' Personal sees ' + (data.count || data.results.length) + ' bookmarks');
|
|
console.log(' My test bookmarks: ' + myTests.length);
|
|
if (myTests.length === 1 && myTests[0].url === 'https://t1.example.com') {
|
|
console.log(' [PASS] Personal only sees my test bookmark');
|
|
return { pass: true, reason: 'Proper user isolation' };
|
|
}
|
|
console.log(' [WARN] Personal sees ' + myTests.length + ' test bookmarks');
|
|
return { pass: null, reason: 'Mixed results - check if sharing enabled' };
|
|
});
|
|
}).then(function(r2) {
|
|
console.log(''); console.log('=== Conflict Resolution ===');
|
|
STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t3.example.com', title: 'T3-Work', description: 'Test', notes: JSON.stringify({ testId: true, path: 'Work' }) })
|
|
.then(function(b1) {
|
|
STATE.KEY = PERSONAL_KEY; STATE.USER = PERSONAL_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t3.example.com', title: 'T3-Personal', description: 'Test', notes: JSON.stringify({ testId: true, path: 'Personal' }) });
|
|
})
|
|
.then(function(b2) {
|
|
console.log(' IDs: ' + b1.id + ' vs ' + b2.id + ' Same? ' + (b1.id === b2.id));
|
|
if (b1.id === b2.id) return { pass: false, reason: 'Server merges by URL' };
|
|
return { pass: true, reason: 'Server creates separate bookmarks' };
|
|
});
|
|
}).then(function(r3) {
|
|
console.log(''); console.log('=== Field Update Behavior ===');
|
|
STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('GET', '/api/bookmarks/?limit=1').then(function(data) {
|
|
var bm = data.results ? data.results[0] : null;
|
|
if (!bm) return API('POST', '/api/bookmarks/', { url: 'https://t4.example.com', title: 'Initial', description: 'Test', notes: JSON.stringify({ testId: true }) });
|
|
return API('PUT', '/api/bookmarks/' + bm.id + '/', { title: 'Work Title', description: 'Work', notes: JSON.stringify({ testId: true, path: 'Work' }) });
|
|
}).then(function(resp) {
|
|
console.log(' Update response: ' + (resp.error ? resp.error : 'OK'));
|
|
return API('GET', '/api/bookmarks/' + (resp.url || (resp.id ? '/api/bookmarks/' + resp.id + '/' : '')));
|
|
}).catch(function() {
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t4-new.example.com', title: 'Initial', description: 'Test', notes: JSON.stringify({ testId: true }) });
|
|
}).then(function(f) {
|
|
console.log(' Final title: ' + f.title);
|
|
if (f.title === 'Work Title') return { pass: true, reason: 'Title was updated' };
|
|
if (f.title === 'Initial') return { pass: true, reason: 'Title NOT updated (notes only)' };
|
|
return { pass: null, reason: 'Unknown title: ' + f.title };
|
|
});
|
|
}).then(function(r4) {
|
|
console.log(''); console.log('=== Delete Behavior ===');
|
|
STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t5.example.com', title: 'T5-Work', description: 'Test', notes: JSON.stringify({ testId: true }) })
|
|
.then(function(b1) {
|
|
STATE.KEY = PERSONAL_KEY; STATE.USER = PERSONAL_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://t5.example.com', title: 'T5-Personal', description: 'Test', notes: JSON.stringify({ testId: true }) });
|
|
})
|
|
.then(function(b2) {
|
|
console.log(' IDs: ' + b1.id + ' vs ' + b2.id + ' Same? ' + (b1.id === b2.id));
|
|
STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('DELETE', '/api/bookmarks/' + b1.id + '/');
|
|
})
|
|
.then(function() {
|
|
STATE.KEY = PERSONAL_KEY; STATE.USER = PERSONAL_USER;
|
|
return API('GET', '/api/bookmarks/?limit=100&url=https://t5.example.com');
|
|
})
|
|
.then(function(data) {
|
|
var count = data.count || data.results ? data.results.length : 0;
|
|
console.log(' Personal sees ' + count + ' with that URL');
|
|
if (count === 0) return { pass: false, reason: 'Delete propagated' };
|
|
return { pass: true, reason: 'Delete isolated' };
|
|
});
|
|
}).then(function(r5) {
|
|
console.log(''); console.log('=== Bundle Filtering ===');
|
|
STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('POST', '/api/bookmarks/', { url: 'https://b6-1.example.com', title: 'B6-W1', description: 'Test', notes: JSON.stringify({ testId: true }) })
|
|
.then(function() {
|
|
return API('POST', '/api/bookmarks/', { url: 'https://b6-2.example.com', title: 'B6-W2', description: 'Test', notes: JSON.stringify({ testId: true }) });
|
|
})
|
|
.then(function() {
|
|
console.log(' Created 2 work bookmarks');
|
|
STATE.KEY = WORK_KEY; STATE.USER = WORK_USER;
|
|
return API('GET', '/api/bookmarks/?all=work&limit=100').then(function(wd) {
|
|
var wc = wd.count || wd.results ? wd.results.length : 0;
|
|
console.log(' Work bundle: ' + wc + ' bookmarks');
|
|
STATE.KEY = PERSONAL_KEY; STATE.USER = PERSONAL_USER;
|
|
return API('GET', '/api/bookmarks/?all=personal&limit=100').then(function(pd) {
|
|
var pc = pd.count || pd.results ? pd.results.length : 0;
|
|
console.log(' Personal bundle: ' + pc + ' bookmarks');
|
|
console.log(' [PASS] Bundle filtering works');
|
|
return { pass: true, reason: 'Bundle filtering works', work: wc, personal: pc };
|
|
});
|
|
});
|
|
});
|
|
}).then(function(r6) {
|
|
// SUMMARY
|
|
console.log(''); console.log('='.repeat(60)); console.log(' Summary'); console.log('='.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;
|
|
console.log(' Total: ' + RESULTS.length); console.log(' Passed: ' + passed); console.log(' Failed: ' + failed); console.log(' Warning: ' + warned);
|
|
console.log('='.repeat(60));
|
|
console.log(''); console.log('LinkdingSyncTests available:'); console.log(' cleanup()'); console.log(' listAll()'); console.log(''); console.log('Done!');
|
|
return RESULTS;
|
|
}).catch(function(e) {
|
|
console.log(''); console.log('Error:', e.message); console.log(''); console.log('Use LinkdingSyncTests.cleanup()');
|
|
return RESULTS;
|
|
});
|
|
|
|
// === CLEANUP ===
|
|
(function cleanup() {
|
|
API('GET', '/api/bookmarks/?limit=100').then(function(data) {
|
|
var tests = data.results ? data.results.filter(function(b) { return b.testId; }) : [];
|
|
console.log(''); console.log('[Cleanup] ' + tests.length + ' test bookmarks');
|
|
if (tests.length) {
|
|
Promise.all(tests.map(function(t) { return API('DELETE', '/api/bookmarks/' + t.id + '/'); })).then(function() { console.log('[Cleanup] Done'); });
|
|
}
|
|
});
|
|
})();
|
|
|
|
// === EXPORT ===
|
|
window.LinkdingSyncTests = { cleanup: (function() { API('GET', '/api/bookmarks/?limit=100').then(function(data) { var t = data.results ? data.results.filter(function(b) { return b.testId; }) : []; console.log('[Cleanup] ' + t.length + ' bookmarks'); if (t.length) Promise.all(t.map(function(tt) { return API('DELETE', '/api/bookmarks/' + tt.id + '/'); })).then(function() { console.log('[Cleanup] Done'); }); })(); }()) };
|
|
|
|
})(window, window.eval);
|
|
|
|
console.log(''); console.log('LinkdingSync Final Test Runner loaded'); console.log(''); console.log('Running tests automatically...'); |