Files
baiyue-logistics-chat/tests/frontend/visitor.test.cjs
T

80 lines
4.2 KiB
JavaScript

const test=require('node:test');
const assert=require('node:assert/strict');
const fs=require('node:fs');
const path=require('node:path');
const vm=require('node:vm');
const {webcrypto}=require('node:crypto');
const flush=()=>new Promise(setImmediate);
function sessionPage(options={}){
const storage=options.storage || new Map(),calls=[],timers=new Map();let timerId=0;
const response=(data,status=200)=>({ok:status>=200&&status<300,status,json:async()=>data});
const context=vm.createContext({console,AbortController,Uint8Array,
window:{crypto:webcrypto},
localStorage:{getItem:key=>{if(options.storageBlocked) throw Error();return storage.get(key);},
setItem:(key,value)=>{if(options.storageBlocked) throw Error();storage.set(key,value);}},
setTimeout:(callback,delay)=>{const id=++timerId;timers.set(id,{callback,delay});return id;},clearTimeout:id=>timers.delete(id),
fetch:async(url,init)=>{calls.push({url,init});
if(options.fetch) return options.fetch(url,init,response);
return url==='/api/visitor/session' ? response({ok:true,key:JSON.parse(init.body).key || 'f'.repeat(64)}) : response([]);
},
});
vm.runInContext(fs.readFileSync(path.join(__dirname,'../../static/js/visitor.js'),'utf8'),context);
return {fetch:context.VisitorSession.fetch,calls,storage,
expire:async()=>{for(const timer of [...timers.values()])timer.callback();await flush();}};
}
test('a local random key is restored across reloads and differs in another browser',async()=>{
const a=sessionPage();await a.fetch('/api/get_history');
const key=JSON.parse(a.calls[0].init.body).key;
assert.match(key,/^[0-9a-f]{64}$/);
const restored=sessionPage({storage:a.storage});await restored.fetch('/api/get_history');
assert.equal(JSON.parse(restored.calls[0].init.body).key,key);
const b=sessionPage();await b.fetch('/api/get_history');
assert.notEqual(JSON.parse(b.calls[0].init.body).key,key);
assert.equal(a.calls[1].init.credentials,'same-origin');
assert.equal(a.calls.some(call=>call.url.includes(key)),false);
});
test('concurrent requests wait for one bootstrap before sending customer data',async()=>{
let release;
const app=sessionPage({fetch:(url,init,response)=>url==='/api/visitor/session'
? new Promise(resolve=>{release=()=>resolve(response({ok:true,key:JSON.parse(init.body).key}));}) : response([])});
const a=app.fetch('/api/get_history'),b=app.fetch('/api/new_chat',{method:'POST'});
assert.equal(app.calls.length,1);
release();await Promise.all([a,b]);
assert.equal(app.calls.filter(call=>call.url==='/api/visitor/session').length,1);
assert.equal(app.calls.length,3);
});
test('blocked local storage uses a server cookie without falling back to global history',async()=>{
const app=sessionPage({storageBlocked:true});await app.fetch('/api/get_history');
assert.equal(JSON.parse(app.calls[0].init.body).key,null);
assert.equal(app.calls[1].init.credentials,'same-origin');
const failed=sessionPage({fetch:(url,init,response)=>response({ok:false},503)});
await assert.rejects(failed.fetch('/api/get_history'));
assert.equal(failed.calls.length,1);
});
test('expired sessions rebootstrap once and retry the original request once',async()=>{
let attempts=0;
const app=sessionPage({fetch:(url,init,response)=>url==='/api/visitor/session'
? response({ok:true,key:JSON.parse(init.body).key}) : response([],++attempts===1 ? 401 : 200)});
const result=await app.fetch('/api/get_history');
assert.equal(result.status,200);
assert.equal(app.calls.filter(call=>call.url==='/api/visitor/session').length,2);
assert.equal(attempts,2);
});
test('a cancelled request waiting for bootstrap does not upload its body',async()=>{
let release;
const app=sessionPage({fetch:(url,init,response)=>new Promise(resolve=>{
release=()=>resolve(response({ok:true,key:JSON.parse(init.body).key}));
})});
const controller=new AbortController();
const pending=app.fetch('/api/upload',{method:'POST',body:'private file',signal:controller.signal});
controller.abort();release();
await assert.rejects(pending,{name:'AbortError'});
assert.equal(app.calls.length,1);
});