59 lines
5.1 KiB
JavaScript
59 lines
5.1 KiB
JavaScript
// Run on test B from a staged release, with api.env loaded. No public telephone destinations.
|
|
import { PrismaClient } from '@prisma/client';
|
|
import { signAccessToken } from '../../packages/auth/dist/index.js';
|
|
import { spawnSync } from 'node:child_process';
|
|
import { writeFile } from 'node:fs/promises';
|
|
import assert from 'node:assert/strict';
|
|
const db = new PrismaClient();
|
|
const fixture = 'cra_20260831';
|
|
const user = await db.user.findFirst({ where:{ status:'ENABLED', userRoles:{some:{roleId:'ROLE_SUPER_ADMIN'}} } });
|
|
assert(user, 'An existing test administrator is required');
|
|
const token = signAccessToken({sub:user.id,username:user.username,roles:['ROLE_SUPER_ADMIN'],typ:'access'}, {secret:process.env.AUTH_ACCESS_TOKEN_SECRET,issuer:process.env.AUTH_TOKEN_ISSUER||'lisglosips-api',audience:process.env.AUTH_TOKEN_AUDIENCE||'lisglosips-web',ttlSeconds:900});
|
|
const get = async (path, authenticated = true) => {
|
|
const response = await fetch(`http://127.0.0.1:3000/api/v2${path}`, {headers:authenticated ? {Authorization:`Bearer ${token}`} : {}});
|
|
return {status:response.status,body:await response.json()};
|
|
};
|
|
try {
|
|
if (process.argv.includes('--setup')) {
|
|
assert.equal(await db.customerGateway.count({where:{sourceIp:'127.0.0.1',id:{not:fixture}}}),0,'Loopback gateway already in use');
|
|
await db.$transaction(async tx => {
|
|
await tx.customer.upsert({where:{id:fixture},create:{id:fixture,name:'主叫分析受控测试客户',balance:100,creditLimit:100},update:{status:'ENABLED'}});
|
|
await tx.vendor.upsert({where:{id:fixture},create:{id:fixture,name:'主叫分析本机模拟供应商'},update:{status:'ENABLED'}});
|
|
await tx.vendorGateway.upsert({where:{id:fixture},create:{id:fixture,vendorId:fixture,name:'主叫分析本机模拟落地',authMode:'IP',host:'127.0.0.1',port:50630,cycleRate:0},update:{status:'ENABLED'}});
|
|
await tx.landingLineGroup.upsert({where:{id:fixture},create:{id:fixture,name:'主叫分析本机测试线路组'},update:{status:'ENABLED'}});
|
|
await tx.landingLineGroupItem.upsert({where:{id:fixture},create:{id:fixture,lineGroupId:fixture,vendorGatewayId:fixture,priority:1},update:{status:'ENABLED'}});
|
|
await tx.customerGateway.upsert({where:{id:fixture},create:{id:fixture,customerId:fixture,name:'主叫分析本机测试入口',authMode:'IP',sourceIp:'127.0.0.1',lineGroupId:fixture,cycleRate:0},update:{status:'ENABLED'}});
|
|
await tx.outboxEvent.create({data:{id:`cra_${Date.now()}`,aggregateType:'customer_gateway_config',aggregateId:fixture,eventType:'CONFIG_CHANGED',payload:{reason:'isolated caller analytics test fixture'}}});
|
|
});
|
|
console.log('Loopback-only zero-rate fixture prepared; waiting for normal config publisher.');
|
|
} else if (process.argv.includes('--disable')) {
|
|
await db.customerGateway.update({where:{id:fixture},data:{status:'DISABLED'}});
|
|
await db.vendorGateway.update({where:{id:fixture},data:{status:'DISABLED'}});
|
|
await db.outboxEvent.create({data:{id:`cra_${Date.now()}`,aggregateType:'customer_gateway_config',aggregateId:fixture,eventType:'CONFIG_CHANGED',payload:{reason:'disable isolated test fixture'}}});
|
|
console.log('Isolated test ingress and egress disabled; evidence retained.');
|
|
} else {
|
|
const from = new Date().toISOString();
|
|
const sip = spawnSync('python3',['tests/api/caller-analytics-sip.py'],{encoding:'utf8',timeout:90000});
|
|
assert.equal(sip.status,0,sip.stderr || sip.stdout);
|
|
console.log(sip.stdout);
|
|
let result;
|
|
for (let i=0;i<12;i++) {
|
|
result = await get(`/caller-analytics/overview?customerId=${fixture}&view=landing&from=${encodeURIComponent(from)}`);
|
|
if (result.status===200 && result.body.summary.totalCalls===6 && result.body.summary.activeCalls===0) break;
|
|
await new Promise(r=>setTimeout(r,1000));
|
|
}
|
|
assert.equal(result.status,200,JSON.stringify(result.body));
|
|
assert.deepEqual(Object.fromEntries(['totalCalls','connectedCalls','answeredCalls','failedCalls'].map(k=>[k,result.body.summary[k]])),{totalCalls:6,connectedCalls:4,answeredCalls:2,failedCalls:2});
|
|
assert.equal((await get('/caller-analytics/overview',false)).status,401);
|
|
assert.equal((await get('/caller-analytics/overview?view=invalid')).status,400);
|
|
assert.equal(result.body.summary.connectedAnswerRate,50);
|
|
const detail = await get(`/caller-analytics/calls?customerId=${fixture}&caller=99100001&view=landing&from=${encodeURIComponent(from)}`);
|
|
assert.equal(detail.status,200); assert.equal(detail.body.rows.length,6);
|
|
const short = detail.body.rows.find(r=>r.callee==='99105');
|
|
assert(short.counts.talkMs>0 && short.counts.talkMs<1000,'Subsecond duration must count as answer');
|
|
const evidence={date:new Date().toISOString(),from,api:result,detail:detail.body,sip:JSON.parse(sip.stdout),tests:['real SIP six scenarios','real API counts and three rates','real DB states','unauthenticated 401','invalid query 400','positive subsecond duration']};
|
|
await writeFile(process.env.CRA_REPORT || '/tmp/caller-analytics-real-evidence.json',JSON.stringify(evidence,null,2));
|
|
console.log(JSON.stringify({passed:true,summary:result.body.summary,shortMs:short.counts.talkMs,quality:result.body.qualityStatus}));
|
|
}
|
|
} finally { await db.$disconnect(); }
|