2026-09-20 13:48:14 -04:00
|
|
|
import { describe, it } from 'node:test';
|
|
|
|
|
import assert from 'node:assert/strict';
|
2026-10-20 19:45:11 -04:00
|
|
|
import {
|
|
|
|
|
describeCallDuration,
|
|
|
|
|
describeCallEnd,
|
|
|
|
|
describeCallDetails,
|
|
|
|
|
parseCallSummaryDetails,
|
|
|
|
|
} from './callSummary';
|
2026-09-20 13:48:14 -04:00
|
|
|
|
|
|
|
|
describe('describeCallDuration', () => {
|
|
|
|
|
it('picks the unit', () => {
|
|
|
|
|
assert.equal(describeCallDuration(40_000), '40 s');
|
|
|
|
|
assert.equal(describeCallDuration(41 * 60_000), '41 min');
|
|
|
|
|
assert.equal(describeCallDuration(65 * 60_000), '1 h 05 min');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('describeCallEnd', () => {
|
|
|
|
|
const min = 60_000;
|
|
|
|
|
it('without a summary shows just the duration', () => {
|
|
|
|
|
assert.equal(describeCallEnd(41 * min), 'Call ended · 41 min');
|
|
|
|
|
});
|
|
|
|
|
it('good / unknown', () => {
|
|
|
|
|
assert.equal(
|
|
|
|
|
describeCallEnd(41 * min, {
|
|
|
|
|
durationMs: 41 * min,
|
|
|
|
|
reconnects: 1,
|
|
|
|
|
poorMs: 0,
|
|
|
|
|
verdict: 'good',
|
|
|
|
|
}),
|
|
|
|
|
'Call ended · 41 min · connection was good',
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
describeCallEnd(2 * min, {
|
|
|
|
|
durationMs: 2 * min,
|
|
|
|
|
reconnects: 0,
|
|
|
|
|
poorMs: 0,
|
|
|
|
|
verdict: 'unknown',
|
|
|
|
|
}),
|
|
|
|
|
'Call ended · 2 min',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
it('reconnects and poor spells', () => {
|
|
|
|
|
assert.equal(
|
|
|
|
|
describeCallEnd(41 * min, {
|
|
|
|
|
durationMs: 41 * min,
|
|
|
|
|
reconnects: 3,
|
|
|
|
|
poorMs: 0,
|
|
|
|
|
verdict: 'fair',
|
|
|
|
|
}),
|
|
|
|
|
'Call ended · 41 min · 3 reconnects',
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
describeCallEnd(12 * min, {
|
|
|
|
|
durationMs: 12 * min,
|
|
|
|
|
reconnects: 1,
|
|
|
|
|
poorMs: 4 * min,
|
|
|
|
|
verdict: 'poor',
|
|
|
|
|
}),
|
|
|
|
|
'Call ended · 12 min · 1 reconnect, connection was poor for 4 min',
|
|
|
|
|
);
|
|
|
|
|
assert.equal(
|
|
|
|
|
describeCallEnd(12 * min, {
|
|
|
|
|
durationMs: 12 * min,
|
|
|
|
|
reconnects: 0,
|
|
|
|
|
poorMs: 2_000,
|
|
|
|
|
verdict: 'fair',
|
|
|
|
|
}),
|
|
|
|
|
'Call ended · 12 min · connection was fair',
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
});
|
2026-10-20 19:45:11 -04:00
|
|
|
|
|
|
|
|
it('[#253] parseCallSummaryDetails keeps well-formed fields and drops the rest', () => {
|
|
|
|
|
const d = parseCallSummaryDetails({
|
|
|
|
|
participants: ['@bob:x', 7, '@carol:x'],
|
|
|
|
|
peakParticipants: 3,
|
|
|
|
|
quality: {
|
|
|
|
|
youPoorMs: 1000,
|
|
|
|
|
othersPoorMs: { '@bob:x': 500, evil: 9, '@c:x': -1 },
|
|
|
|
|
everyonePoorMs: 0,
|
|
|
|
|
},
|
|
|
|
|
network: { rttMedianMs: 38, rttP95Ms: 'fast', inboundLossPct: 0.5 },
|
|
|
|
|
activity: { speakingMs: 60_000, mutedMs: 'x', screenshareMs: 0 },
|
|
|
|
|
});
|
|
|
|
|
assert.deepEqual(d, {
|
|
|
|
|
participants: ['@bob:x', '@carol:x'],
|
|
|
|
|
peakParticipants: 3,
|
|
|
|
|
quality: { youPoorMs: 1000, othersPoorMs: { '@bob:x': 500 }, everyonePoorMs: 0 },
|
|
|
|
|
network: { rttMedianMs: 38, inboundLossPct: 0.5 },
|
|
|
|
|
activity: { speakingMs: 60_000, mutedMs: 0, screenshareMs: 0 },
|
|
|
|
|
});
|
|
|
|
|
assert.equal(parseCallSummaryDetails(undefined), undefined);
|
|
|
|
|
assert.equal(parseCallSummaryDetails('details'), undefined);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('[#253] describeCallDetails: technical sections with names, blame and numbers', () => {
|
|
|
|
|
const names: Record<string, string> = { '@bob:x': 'Bob', '@carol:x': 'Carol' };
|
|
|
|
|
const sections = describeCallDetails(
|
|
|
|
|
{
|
|
|
|
|
durationMs: 2_460_000,
|
|
|
|
|
reconnects: 2,
|
|
|
|
|
poorMs: 120_000,
|
|
|
|
|
verdict: 'fair',
|
|
|
|
|
details: {
|
|
|
|
|
participants: ['@bob:x', '@carol:x'],
|
|
|
|
|
peakParticipants: 3,
|
|
|
|
|
quality: {
|
|
|
|
|
youPoorMs: 120_000,
|
|
|
|
|
othersPoorMs: { '@carol:x': 30_000 },
|
|
|
|
|
everyonePoorMs: 10_000,
|
|
|
|
|
},
|
|
|
|
|
network: {
|
|
|
|
|
rttMedianMs: 38,
|
|
|
|
|
rttP95Ms: 310,
|
|
|
|
|
outboundLossPct: 4.1,
|
|
|
|
|
inboundLossPct: 0.5,
|
|
|
|
|
jitterMs: 6,
|
|
|
|
|
},
|
|
|
|
|
activity: { speakingMs: 600_000, mutedMs: 1_200_000, screenshareMs: 300_000 },
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
2_460_000,
|
|
|
|
|
90_000,
|
|
|
|
|
(u) => names[u] ?? u,
|
|
|
|
|
);
|
|
|
|
|
assert.deepEqual(sections, [
|
|
|
|
|
{ title: 'Call', lines: ['Duration 41 min · peak 3 in the call', 'With Bob, Carol'] },
|
|
|
|
|
{
|
|
|
|
|
title: 'Connection',
|
|
|
|
|
lines: [
|
|
|
|
|
'Verdict: fair · 2 reconnects',
|
|
|
|
|
'Your connection poor/lost: 2 min',
|
|
|
|
|
'Carol poor/lost: 30 s',
|
|
|
|
|
'Everyone poor at once: 10 s (likely server or SFU side)',
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'Network',
|
|
|
|
|
lines: [
|
|
|
|
|
'RTT median 38 ms · p95 310 ms',
|
|
|
|
|
'Outbound packet loss 4.1%',
|
|
|
|
|
'Inbound packet loss 0.5%',
|
|
|
|
|
'Jitter 6 ms',
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
title: 'You',
|
|
|
|
|
lines: ['Talking 10 min · muted 20 min · deafened 2 min', 'Screensharing 5 min'],
|
|
|
|
|
},
|
|
|
|
|
]);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('[#253] describeCallDetails without fork details (older fork) still says something useful', () => {
|
|
|
|
|
const sections = describeCallDetails(
|
|
|
|
|
{ durationMs: 60_000, reconnects: 0, poorMs: 0, verdict: 'good' },
|
|
|
|
|
60_000,
|
|
|
|
|
0,
|
|
|
|
|
(u) => u,
|
|
|
|
|
);
|
|
|
|
|
assert.deepEqual(
|
|
|
|
|
sections.map((s) => s.title),
|
|
|
|
|
['Call', 'Connection'],
|
|
|
|
|
);
|
|
|
|
|
assert.deepEqual(sections[0].lines, ['Duration 1 min', 'Participants unknown']);
|
|
|
|
|
});
|