调整客服自动接待和手动排队规则
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
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');
|
||||
});
|
||||
Reference in New Issue
Block a user