257 lines
8.6 KiB
JavaScript
257 lines
8.6 KiB
JavaScript
/*
|
|
* 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'); |