Add store/email/phone filtering, richer call-line formatting, CDR feed pagination and queueing, and split Jira poller enrichment into testable modules. Co-authored-by: Cursor <cursoragent@cursor.com>
184 lines
5.1 KiB
JavaScript
184 lines
5.1 KiB
JavaScript
// tests/callReport.join.test.js
|
|
|
|
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
|
|
import { buildScopedCallFilter } from '../services/callReport/filterCallsByTarget.js';
|
|
import { joinCallQuality, worstPrismaBucketForCall } from '../services/callReport/joinCallQuality.js';
|
|
import { CALL_BUCKETS } from '../services/callReport/groupCdrCalls.js';
|
|
|
|
const window = {
|
|
startTime: '2026-07-23T13:00:00.000Z',
|
|
endTime: '2026-07-24T01:00:00.000Z',
|
|
};
|
|
|
|
const INBOUND_PHONE_LEGS = [
|
|
{
|
|
'Start time': '2026-07-23T13:00:00.000Z',
|
|
'Release time': '2026-07-23T13:05:00.000Z',
|
|
'Call type': 'SIP_INBOUND',
|
|
'Correlation ID': 'corr-1',
|
|
'Report ID': 'r1',
|
|
'Calling number': '+15551234567',
|
|
'Called number': '+12122194600',
|
|
'Site main number': '+12122194600',
|
|
'Call outcome': 'Success',
|
|
'Call outcome reason': 'Normal',
|
|
'Answer indicator': 'Yes',
|
|
Answered: 'true',
|
|
Duration: 300,
|
|
},
|
|
{
|
|
'Start time': '2026-07-23T13:00:30.000Z',
|
|
'Call type': 'SIP_ENTERPRISE',
|
|
'Correlation ID': 'corr-1',
|
|
'Report ID': 'r2',
|
|
'User type': 'User',
|
|
User: 'Store 02477',
|
|
Model: 'DBS-210-3PC',
|
|
'Called number': '52477',
|
|
'Calling number': '+15551234567',
|
|
'Call outcome': 'Success',
|
|
'Call outcome reason': 'Normal',
|
|
'Answer indicator': 'Yes',
|
|
Answered: 'true',
|
|
Duration: 270,
|
|
},
|
|
];
|
|
|
|
test('joinCallQuality groups legs into correlated calls', () => {
|
|
const joined = joinCallQuality({
|
|
cdrItems: [
|
|
...INBOUND_PHONE_LEGS,
|
|
{
|
|
Direction: 'Outbound',
|
|
'Start time': '2026-07-23T15:00:00.000Z',
|
|
Duration: 10,
|
|
'Answer indicator': 'No',
|
|
'Report ID': 'r3',
|
|
'Call type': 'SIP_OUTBOUND',
|
|
'Call outcome': 'Failed',
|
|
'Call outcome reason': 'Busy',
|
|
},
|
|
],
|
|
appAudio: null,
|
|
window,
|
|
});
|
|
assert.equal(joined.summary.total, 2);
|
|
assert.equal(joined.summary.inbound, 1);
|
|
assert.equal(joined.summary.outbound, 1);
|
|
assert.equal(joined.summary.abnormal, 1);
|
|
assert.equal(joined.abnormalCalls.length, 1);
|
|
assert.equal(joined.buckets[CALL_BUCKETS.inboundReachedPhone].length, 1);
|
|
});
|
|
|
|
test('joinCallQuality attaches WAN only for answered inbound', () => {
|
|
const appAudio = {
|
|
mos: { values: [4.4, 4.4, 3.1, 4.4, 4.4] },
|
|
loss: { values: [0, 0, 2, 0, 0] },
|
|
jitter: { values: [5, 5, 5, 5, 5] },
|
|
};
|
|
const joined = joinCallQuality({
|
|
cdrItems: INBOUND_PHONE_LEGS,
|
|
appAudio,
|
|
window,
|
|
});
|
|
assert.ok(joined.calls[0].wan);
|
|
assert.equal(joined.calls[0].wan.mos, 4.4);
|
|
});
|
|
|
|
test('joinCallQuality skips WAN for unanswered outbound', () => {
|
|
const appAudio = {
|
|
mos: { values: [4.0, 4.0, 4.0] },
|
|
loss: { values: [0, 0, 0] },
|
|
jitter: { values: [5, 5, 5] },
|
|
};
|
|
const joined = joinCallQuality({
|
|
cdrItems: [{
|
|
'Start time': '2026-07-23T15:00:00.000Z',
|
|
'Report ID': 'r4',
|
|
'Call type': 'SIP_OUTBOUND',
|
|
'Call outcome': 'Failed',
|
|
'Call outcome reason': 'Busy',
|
|
'Answer indicator': 'No',
|
|
}],
|
|
appAudio,
|
|
window,
|
|
});
|
|
assert.equal(joined.calls[0].wan, null);
|
|
});
|
|
|
|
test('worstPrismaBucketForCall picks low MOS bucket', () => {
|
|
const call = {
|
|
start: '2026-07-23T13:05:00.000Z',
|
|
duration: 600,
|
|
};
|
|
const appAudio = {
|
|
mos: { values: [4.4, 4.4, 3.1, 4.4, 4.4] },
|
|
loss: { values: [0, 0, 2, 0, 0] },
|
|
jitter: { values: [5, 5, 5, 5, 5] },
|
|
};
|
|
const w = worstPrismaBucketForCall(call, appAudio, window);
|
|
assert.ok(w);
|
|
assert.equal(w.mos, 3.1);
|
|
});
|
|
|
|
test('worstPrismaBucketForCall matches Prisma datapoint timestamps', () => {
|
|
const call = {
|
|
start: '2026-07-23T15:18:00.000Z',
|
|
duration: 455,
|
|
};
|
|
const appAudio = {
|
|
mos: {
|
|
interval: '5min',
|
|
values: [4.4, 4.3, 3.1],
|
|
timedPoints: [
|
|
{ time: '2026-07-23T13:00:00.000Z', value: 4.4 },
|
|
{ time: '2026-07-23T13:05:00.000Z', value: null },
|
|
{ time: '2026-07-23T15:15:00.000Z', value: 4.3 },
|
|
{ time: '2026-07-23T15:20:00.000Z', value: 3.1 },
|
|
],
|
|
},
|
|
loss: {
|
|
timedPoints: [
|
|
{ time: '2026-07-23T15:15:00.000Z', value: 0 },
|
|
{ time: '2026-07-23T15:20:00.000Z', value: 2 },
|
|
],
|
|
},
|
|
jitter: {
|
|
timedPoints: [
|
|
{ time: '2026-07-23T15:15:00.000Z', value: 1.2 },
|
|
{ time: '2026-07-23T15:20:00.000Z', value: 3.4 },
|
|
],
|
|
},
|
|
};
|
|
const w = worstPrismaBucketForCall(call, appAudio, window);
|
|
assert.ok(w);
|
|
assert.equal(w.mos, 3.1);
|
|
assert.equal(w.loss, 2);
|
|
assert.equal(w.jitter, 3.4);
|
|
});
|
|
|
|
test('joinCallQuality filters scoped calls when filter provided', () => {
|
|
const joined = joinCallQuality({
|
|
cdrItems: [
|
|
...INBOUND_PHONE_LEGS,
|
|
{
|
|
Direction: 'Outbound',
|
|
'Start time': '2026-07-23T15:00:00.000Z',
|
|
Duration: 10,
|
|
'Answer indicator': 'No',
|
|
'Report ID': 'r3',
|
|
'Call type': 'SIP_OUTBOUND',
|
|
'Call outcome': 'Failed',
|
|
'Call outcome reason': 'Busy',
|
|
},
|
|
],
|
|
appAudio: null,
|
|
window,
|
|
filter: buildScopedCallFilter({ kind: 'number', extension: '52477' }),
|
|
});
|
|
assert.equal(joined.summary.total, 1);
|
|
assert.equal(joined.summary.inbound, 1);
|
|
assert.equal(joined.summary.outbound, 0);
|
|
});
|