Files

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...');