94 lines
2.5 KiB
TypeScript
94 lines
2.5 KiB
TypeScript
/*
|
|||
|
|
Copyright 2026 Lotus Guild
|
||
|
|
|
||
|
|
SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||
|
|
Please see LICENSE in the repository root for full details.
|
||
|
|
*/
|
||
|
|
|
||
|
|
import { ConnectionQuality } from "livekit-client";
|
||
|
|
import { describe, expect, it } from "vitest";
|
||
|
|
|
||
|
|
import { CallQualityTracker } from "./lotusCallSummary";
|
||
|
|
|
||
|
|
const clock = (): { now: () => number; tick: (ms: number) => void } => {
|
||
|
|
let t = 1_000_000;
|
||
|
|
return {
|
||
|
|
now: () => t,
|
||
|
|
tick: (ms) => {
|
||
|
|
t += ms;
|
||
|
|
},
|
||
|
|
};
|
||
|
|
};
|
||
|
|
|
||
|
|
describe("CallQualityTracker", () => {
|
||
|
|
it("reports unknown when nothing was sampled", () => {
|
||
|
|
const c = clock();
|
||
|
|
const tr = new CallQualityTracker(c.now);
|
||
|
|
tr.start();
|
||
|
|
c.tick(60_000);
|
||
|
|
expect(tr.summary()).toEqual({
|
||
|
|
durationMs: 60_000,
|
||
|
|
reconnects: 0,
|
||
|
|
poorMs: 0,
|
||
|
|
verdict: "unknown",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("is good when quality stayed fine", () => {
|
||
|
|
const c = clock();
|
||
|
|
const tr = new CallQualityTracker(c.now);
|
||
|
|
tr.start();
|
||
|
|
tr.setQuality(ConnectionQuality.Excellent);
|
||
|
|
c.tick(30 * 60_000);
|
||
|
|
expect(tr.summary().verdict).toBe("good");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("accumulates poor time across episodes, including an open one", () => {
|
||
|
|
const c = clock();
|
||
|
|
const tr = new CallQualityTracker(c.now);
|
||
|
|
tr.start();
|
||
|
|
tr.setQuality(ConnectionQuality.Good);
|
||
|
|
c.tick(60_000);
|
||
|
|
tr.setQuality(ConnectionQuality.Poor);
|
||
|
|
c.tick(10_000);
|
||
|
|
tr.setQuality(ConnectionQuality.Good);
|
||
|
|
c.tick(60_000);
|
||
|
|
tr.setQuality(ConnectionQuality.Lost);
|
||
|
|
c.tick(5_000);
|
||
|
|
const s = tr.summary();
|
||
|
|
expect(s.poorMs).toBe(15_000);
|
||
|
|
expect(s.durationMs).toBe(135_000);
|
||
|
|
expect(s.verdict).toBe("fair");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("is poor with many reconnects or mostly-poor quality", () => {
|
||
|
|
const c = clock();
|
||
|
|
const tr = new CallQualityTracker(c.now);
|
||
|
|
tr.start();
|
||
|
|
tr.setQuality(ConnectionQuality.Good);
|
||
|
|
for (let i = 0; i < 4; i += 1) tr.reconnect();
|
||
|
|
c.tick(60_000);
|
||
|
|
expect(tr.summary()).toMatchObject({ reconnects: 4, verdict: "poor" });
|
||
|
|
|
||
|
|
const tr2 = new CallQualityTracker(c.now);
|
||
|
|
tr2.start();
|
||
|
|
tr2.setQuality(ConnectionQuality.Poor);
|
||
|
|
c.tick(60_000);
|
||
|
|
expect(tr2.summary().verdict).toBe("poor");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("ignores unknown samples and only starts once", () => {
|
||
|
|
const c = clock();
|
||
|
|
const tr = new CallQualityTracker(c.now);
|
||
|
|
tr.start();
|
||
|
|
c.tick(1_000);
|
||
|
|
tr.start();
|
||
|
|
tr.setQuality(ConnectionQuality.Unknown);
|
||
|
|
c.tick(1_000);
|
||
|
|
expect(tr.summary()).toMatchObject({
|
||
|
|
durationMs: 2_000,
|
||
|
|
verdict: "unknown",
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|