Files
werkjournal/tests/frontend/push-settings.test.cjs
2026-09-09 11:56:36 +02:00

43 lines
3.3 KiB
JavaScript

const {test} = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const vm = require('node:vm');
const source = fs.readFileSync('src/main/frontend/push-settings.js', 'utf8').replace('export function', 'function');
const settle = () => new Promise(resolve => setImmediate(resolve));
function setup({permission='granted', enabled=true}={}) {
const elements=[];
const create = tag => { const node={tag, children:[], listeners:{}, append(...items){this.children.push(...items)}, replaceChildren(){this.children=[]},
setAttribute(){}, addEventListener(name, callback){this.listeners[name]=callback}}; elements.push(node); return node; };
const requests=[]; let permissionCalls=0, subscriptions=0, unsubscribed=0;
const subscription={toJSON:()=>({endpoint:'https://fcm.googleapis.com/wp/test',keys:{p256dh:'public',auth:'auth'}}), async unsubscribe(){unsubscribed++;}};
const env={isSecureContext:true, PushManager: function(){}, Notification:{async requestPermission(){permissionCalls++;return permission}},
navigator:{serviceWorker:{ready:Promise.resolve({pushManager:{async getSubscription(){return subscriptions?subscription:null},async subscribe(){subscriptions++;return subscription}}})}},
atob: value=>Buffer.from(value,'base64').toString('binary'),setTimeout,clearTimeout,
async fetch(url, options){requests.push({url,...options});return {ok:true,async json(){return {enabled:options.method==='POST',version:options.method==='GET'?null:1}}}}};
const context={window:env,document:{createElement:create,createTextNode:text=>({text})},Intl,URL,Uint8Array,Promise,Error};
vm.runInNewContext(source,context);
const root=create('div');
env.werkjournalPushSettings(root,{german:false,enabled,publicKey:'BA',url:'/account/push',csrfHeader:'X-CSRF-TOKEN',csrf:'csrf'});
return {elements,requests,toggle:()=>elements.find(e=>e.tag==='input'),permissionCalls:()=>permissionCalls,subscriptions:()=>subscriptions,unsubscribed:()=>unsubscribed};
}
test('viewing settings never requests permission; opt-in persists only this device', async()=>{
const f=setup();await settle();assert.equal(f.permissionCalls(),0);
const toggle=f.toggle();assert.equal(toggle.disabled,false);toggle.checked=true;
await toggle.listeners.change();assert.equal(f.permissionCalls(),1);assert.equal(f.subscriptions(),1);
const post=f.requests.find(r=>r.method==='POST');assert.equal(post.headers['X-CSRF-TOKEN'],'csrf');
assert.equal(JSON.parse(post.body).expectedVersion,null);assert.equal(toggle.checked,true);
toggle.checked=false;await toggle.listeners.change();assert.equal(f.unsubscribed(),1);
assert.ok(f.requests.some(r=>r.method==='DELETE'&&r.url.endsWith('expectedVersion=1')));
});
test('denied permission never creates a subscription or server mutation',async()=>{
const f=setup({permission:'denied'});await settle();const toggle=f.toggle();toggle.checked=true;
await toggle.listeners.change();assert.equal(f.subscriptions(),0);assert.equal(f.requests.length,1);
assert.equal(toggle.checked,false);assert.ok(f.elements.some(e=>e.textContent?.includes('not allowed')));
});
test('disabled server does not expose an active opt-in control',async()=>{
const f=setup({enabled:false});await settle();assert.equal(f.toggle(),undefined);assert.equal(f.requests.length,0);
});