Files
opportunities-extension/tests/internalSrSession.test.cjs

70 lines
3.6 KiB
JavaScript

const assert = require('node:assert/strict');
const { test } = require('node:test');
const fs = require('node:fs');
const vm = require('node:vm');
const factory = vm.runInNewContext(fs.readFileSync('src/features/opportunities/internal-sr-session.js', 'utf8') + '\ncreateInternalSrSession');
test('without a document, session recovery stops without polling', async () => {
for (const result of [{ok:false,httpStatus:401}, {ok:false,debugRequests:[{httpStatus:401}]}, {ok:false,error:'Request failed with status 401.'}]) {
let calls=0;
const service=factory({query:async()=>{calls++; return result;},environment:{document:{createElement(){assert.fail('No iframe');}},setTimeout(){assert.fail('No polling');}}});
await assert.rejects(service.request('123'), /Internal SR|status 401/);
await new Promise(resolve=>setImmediate(resolve));
assert.equal(calls,1);
}
});
function harness() {
const timers = [], listeners = new Map();
let appended = 0, removed = 0;
const frame = {style:{},setAttribute(){},addEventListener(k,v){listeners.set(k,v);},removeEventListener(k){listeners.delete(k);},remove(){removed++;}};
return { timers, frame, listeners, counts:()=>({appended,removed}), environment:{
document:{createElement:()=>frame,body:{append(){appended++;}}},
setTimeout(fn,delay){timers.push({fn,delay});}
}};
}
const flush = () => new Promise(resolve=>setImmediate(resolve));
test('hidden iframe remains alive after load and closes only after successful session query',async()=>{
const h=harness(); let calls=0;
const api=factory({environment:h.environment,query:async()=>++calls<3?{ok:false,httpStatus:401}:{ok:true,items:[{SrId:1}]}});
const first=api.request('123'); assert.equal(first,api.request(123));
await flush();
assert.equal(h.counts().appended,1);
h.listeners.get('load')?.();
assert.equal(h.counts().removed,0);
assert.equal(calls,1);
assert.equal(h.timers[0].delay,2000);
h.timers.shift().fn(); await flush();
assert.equal(h.counts().removed,0);
assert.equal(h.timers[0].delay,4000);
h.timers.shift().fn();
assert.equal((await first)[0].SrId,1);
assert.equal(h.counts().removed,1);
});
test('persistent 401 is bounded to five recovery checks and removes iframe',async()=>{
const h=harness(); let calls=0;
const api=factory({environment:h.environment,query:async()=>{calls++;return {ok:false,httpStatus:401};}});
const failed=assert.rejects(api.request('123'),/still unavailable/);
for(const delay of [2000,4000,8000,12000,16000]) {
await flush(); assert.equal(h.timers[0].delay,delay); h.timers.shift().fn();
}
await failed;
assert.equal(calls,6);
assert.equal(h.counts().removed,1);
assert.equal(h.timers.length,0);
});
test('concurrent requests share work and explicit retry can recover', async () => {
let calls=0, complete;
const service=factory({query:()=>{calls++; return new Promise(resolve=>{complete=resolve;});}});
const first=service.request('123'); const second=service.request(123);
assert.equal(first,second); await Promise.resolve();
complete({ok:false,httpStatus:401}); await assert.rejects(first);
const retry=service.request('123'); await Promise.resolve(); complete({ok:true,items:[{SrId:'456'}]});
assert.equal((await retry)[0].SrId,'456'); assert.equal(calls,2);
});
test('other errors do not retry and successful empty results are preserved', async()=>{
const failed=factory({query:async()=>({ok:false,error:'Forbidden',httpStatus:403})});
await assert.rejects(failed.request('123'),/Forbidden/);
const ok=factory({query:async()=>({ok:true,items:[]})}); assert.equal((await ok.request('123')).length,0);
});