72 lines
3.3 KiB
JavaScript
72 lines
3.3 KiB
JavaScript
const {test} = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const vm = require('node:vm');
|
|
const fs = require('node:fs');
|
|
const source = fs.readFileSync('src/main/frontend/account-session.js','utf8');
|
|
test('only trusted visible interaction renews, throttles successes, and retries failures', async () => {
|
|
let now = 100000;
|
|
let calls = 0;
|
|
let fail = false;
|
|
const listeners = {};
|
|
const window = {};
|
|
const document = {visibilityState:'visible',addEventListener:(name,fn) => listeners[name]=fn};
|
|
vm.runInNewContext(source,{window,document,AbortController,Date:{now:()=>now},fetch:async (url,options) => {
|
|
calls++;
|
|
assert.equal(url,'/account/activity');
|
|
assert.equal(options.method,'POST');
|
|
assert.equal(options.headers['X-CSRF-TOKEN'],'test-csrf');
|
|
if (fail) throw Error('offline');
|
|
return {status:204};
|
|
}});
|
|
window.werkjournalActivity('/account/activity','X-CSRF-TOKEN','test-csrf');
|
|
assert.equal(calls,0);
|
|
await listeners.pointerdown({isTrusted:false});
|
|
document.visibilityState='hidden';
|
|
await listeners.keydown({isTrusted:true});
|
|
assert.equal(calls,0);
|
|
document.visibilityState='visible';
|
|
await listeners.pointerdown({isTrusted:true});
|
|
await listeners.keydown({isTrusted:true});
|
|
assert.equal(calls,1);
|
|
now += 60000; fail=true;
|
|
await listeners.keydown({isTrusted:true});
|
|
assert.equal(calls,2);
|
|
fail=false;
|
|
await listeners.keydown({isTrusted:true});
|
|
assert.equal(calls,3);
|
|
});
|
|
test('unavailable local storage does not prevent the CSRF-protected logout submission', () => {
|
|
const window = {};
|
|
let submitted = false;
|
|
let form;
|
|
const document = {createElement: tag => tag === 'form' ? (form={append(input){this.input=input;},submit(){submitted=true;}}) : {},body:{append(){}}};
|
|
vm.runInNewContext(source,{window,document,get localStorage(){throw Error('storage blocked');}});
|
|
window.werkjournalLogout('/account/logout-all','_csrf','test-token','owner');
|
|
assert.equal(submitted,true);
|
|
assert.equal(form.method,'POST');
|
|
assert.equal(form.action,'/account/logout-all');
|
|
assert.equal(form.input.name,'_csrf');
|
|
assert.equal(form.input.value,'test-token');
|
|
});
|
|
test('revocation clears only owner drafts, notifies other tabs and returns to login', async () => {
|
|
const listeners = {};
|
|
let destination;
|
|
const store = {'werkjournal.work.alice.new-2026-09-09.tab':'draft',
|
|
'werkjournal.work.bob.new-2026-09-09.tab':'other draft', 'werkjournal.theme':'dark'};
|
|
Object.defineProperties(store, {
|
|
removeItem:{value:key=>delete store[key]},
|
|
setItem:{value:(key,value)=>store[key]=value}
|
|
});
|
|
const window = {location:{assign:url=>destination=url}};
|
|
const document = {visibilityState:'visible',addEventListener:(name,fn)=>listeners[name]=fn};
|
|
vm.runInNewContext(source,{window,document,localStorage:store,AbortController,
|
|
crypto:{randomUUID:()=> 'logout-event'},Date:{now:()=>100000},fetch:async()=>({status:401})});
|
|
window.werkjournalActivity('/app/account/activity','X-CSRF-TOKEN','test-token','alice');
|
|
await listeners.pointerdown({isTrusted:true});
|
|
assert.equal(store['werkjournal.work.alice.new-2026-09-09.tab'],undefined);
|
|
assert.equal(store['werkjournal.work.bob.new-2026-09-09.tab'],'other draft');
|
|
assert.equal(store['werkjournal.theme'],'dark');
|
|
assert.equal(store['werkjournal.logout.alice'],'logout-event');
|
|
assert.equal(destination,'/app/login');
|
|
});
|