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

129 lines
6.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 path = require('node:path');
const source = fs.readFileSync(path.join(__dirname, '../../static/js/admin_routing.js'), 'utf8');
function snapshot(online = false){
return {ok:true, username:'alice', presence:{online},
groups:[{id:1,name:'综合客服'},{id:2,name:'售后组'}],
agents:[{username:'alice',name:'客服甲',group_id:1,group_name:'综合客服',online,active_count:1},
{username:'bob',name:'客服乙',group_id:2,group_name:'售后组',online:true,active_count:0}],
sessions:[{id:7,group_id:1,group_name:'综合客服',owner_username:'alice',owner_name:'客服甲',status:'active',version:3}],
events:[]};
}
function desk(){
const elements = new Map(), calls = [], hooks = {};
function element(id){
if(!elements.has(id)) elements.set(id, {value:'',innerHTML:'',textContent:'',hidden:false,disabled:false,
dataset:{},attributes:{},listeners:{},classList:{toggle(){},add(){},remove(){}},
setAttribute(name,value){this.attributes[name]=value;},
addEventListener(name,listener){this.listeners[name]=listener;}});
return elements.get(id);
}
const context = vm.createContext({window:{},document:{getElementById:element},console});
vm.runInContext(source,context);
const api = context.window.AdminRouting.create({username:'alice',
request:async(url,options)=>{
const data = options && options.body ? JSON.parse(options.body) : undefined;
calls.push({url,data});
if(hooks.request) return hooks.request(url,data);
if(url === '/api/admin/routing') return snapshot();
return {ok:true,online:data && data.online};
},changed(){calls.push({changed:true});}});
return {api,element,calls,hooks};
}
test('loading the desk shows offline without silently enabling automatic assignment',async()=>{
const app = desk();
await app.api.refresh();
assert.match(app.element('routing-presence-text').textContent,/离线/);
assert.equal(app.calls.filter(item=>item.data && item.data.online === true).length,0);
assert.match(app.element('routing-members').innerHTML,/客服乙/);
assert.match(app.element('routing-members').innerHTML,/售后组/);
});
test('heartbeat continues while offline and never sends an online claim',async()=>{
const app = desk();
await app.api.refresh();
await app.api.heartbeat();
const sent = app.calls.find(item=>item.url === '/api/admin/routing/presence');
assert.deepEqual(sent.data,{});
assert.match(app.element('routing-presence-text').textContent,/离线/);
});
test('failed presence change retains the confirmed status and provides retry feedback',async()=>{
const app = desk();
await app.api.refresh();
app.hooks.request=async(url)=>{ if(url.endsWith('/presence')) throw new Error('network'); return snapshot(); };
await app.api.setPresence();
assert.match(app.element('routing-presence-text').textContent,/离线/);
assert.match(app.element('routing-status').textContent,/未确认/);
assert.equal(app.element('routing-presence-toggle').disabled,false);
});
test('changing group only offers online members of that group for reassignment',async()=>{
const app = desk();
await app.api.refresh();
app.element('routing-session').value='7';
app.element('routing-session').listeners.change();
app.element('routing-target-group').value='2';
app.element('routing-target-group').listeners.change();
assert.match(app.element('routing-target-owner').innerHTML,/bob/);
assert.doesNotMatch(app.element('routing-target-owner').innerHTML,/alice/);
});
test('refresh does not silently rebase the selected assignment version',async()=>{
const app = desk();
await app.api.refresh();
app.element('routing-session').value='7';
app.element('routing-session').listeners.change();
app.element('routing-target-group').value='2';
app.element('routing-target-owner').value='bob';
const updated = snapshot(); updated.sessions[0].version=4;
app.hooks.request=async(url)=>url === '/api/admin/routing' ? updated : {ok:true};
await app.api.refresh();
await app.api.assign();
const sent=app.calls.find(item=>item.url === '/api/admin/routing/assign');
assert.equal(sent.data.version,3);
assert.equal(sent.data.username,'bob');
});
test('a late background refresh cannot override a confirmed presence switch',async()=>{
const app=desk(); await app.api.refresh();
let resolve;
app.hooks.request=async(url)=>url === '/api/admin/routing' ? new Promise(done=>{resolve=done;}) : {ok:true,online:true};
const pending=app.api.refresh();
const switching=app.api.setPresence();
await switching;
resolve(snapshot(false)); await pending;
assert.match(app.element('routing-presence-text').textContent,/在线/);
});
test('a manual refresh during a presence write cannot restore an older availability state',async()=>{
const app=desk(); await app.api.refresh();
let confirmWrite, confirmRead;
app.hooks.request=(url)=>url === '/api/admin/routing' ? new Promise(done=>{confirmRead=done;}) :
new Promise(done=>{confirmWrite=done;});
const switching=app.api.setPresence();
const refreshing=app.api.refresh();
confirmWrite({ok:true,online:true}); await switching;
if(confirmRead) confirmRead(snapshot(false));
await refreshing;
assert.match(app.element('routing-presence-text').textContent,/在线/);
});
test('an unavailable selected owner is not silently replaced with automatic queueing',async()=>{
const app=desk(); await app.api.refresh();
app.element('routing-session').value='7'; app.element('routing-session').listeners.change();
app.element('routing-target-group').value='2'; app.element('routing-target-group').listeners.change();
app.element('routing-target-owner').value='bob';
const updated=snapshot(); updated.agents[1].online=false;
app.hooks.request=async(url)=>url === '/api/admin/routing' ? updated : {ok:true};
await app.api.refresh(); await app.api.assign();
const sent=app.calls.find(item=>item.url === '/api/admin/routing/assign');
assert.equal(sent.data.username,'bob');
});