Files
werkjournal/tests/frontend/account-session.test.cjs
2026-09-09 09:37:33 +02:00

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