import { describe, it, expect, beforeEach, afterEach } from "@jest/globals"; import { getDashboardStats } from "../dashboard/calls.dashboard"; import { getCallsCollection } from "../calls.getters"; import { getClientsConfigCollection } from "../../clientsConfig/clientsConfig.getters"; describe("getDashboardStats", () => { beforeEach(async () => { await getCallsCollection().deleteMany({}); await getClientsConfigCollection().deleteMany({}); }); afterEach(async () => { await getCallsCollection().deleteMany({}); await getClientsConfigCollection().deleteMany({}); }); it("should return correct empty dashboard stats when no calls exist", async () => { const clientId = "client-dash-123"; await getClientsConfigCollection().insertOne({ clientId, timezone: "UTC", products: {}, } as any); const params = { clientId, startDate: "2026-05-01T00:00:00.000Z", endDate: "2026-05-31T23:59:59.999Z", }; const result = await getDashboardStats(params); expect(result).toBeDefined(); expect(result.kpis.totalCalls).toBe(0); expect(result.kpis.completedCount).toBe(0); expect(result.kpis.avgDurationSeconds).toBe(0); expect(result.kpis.inCallCount).toBe(0); expect(result.kpis.timeSavedMinutes).toBe(0); // ~30 day range -> daily buckets, gap-filled with zeros across the whole range expect(result.charts.volumeGranularity).toBe("day"); expect(result.charts.volumeData.length).toBeGreaterThan(0); expect(result.charts.volumeData.every((d) => d.completed === 0)).toBe(true); expect(result.charts.volumeData[0].date).toBe("2026-05-01"); expect( result.charts.volumeData[result.charts.volumeData.length - 1].date, ).toBe("2026-05-31"); }); it("should pick a bucket size that keeps the volume chart readable across range lengths", async () => { const clientId = "client-dash-456"; await getClientsConfigCollection().insertOne({ clientId, timezone: "UTC", products: {}, } as any); const oneDay = await getDashboardStats({ clientId, startDate: "2026-05-01T00:00:00.000Z", endDate: "2026-05-01T23:59:59.999Z", }); expect(oneDay.charts.volumeGranularity).toBe("hour"); const threeWeeks = await getDashboardStats({ clientId, startDate: "2026-05-01T00:00:00.000Z", endDate: "2026-05-21T23:59:59.999Z", }); expect(threeWeeks.charts.volumeGranularity).toBe("day"); const fourMonths = await getDashboardStats({ clientId, startDate: "2026-01-01T00:00:00.000Z", endDate: "2026-04-30T23:59:59.999Z", }); expect(fourMonths.charts.volumeGranularity).toBe("week"); const eighteenMonths = await getDashboardStats({ clientId, startDate: "2025-01-01T00:00:00.000Z", endDate: "2026-06-30T23:59:59.999Z", }); expect(eighteenMonths.charts.volumeGranularity).toBe("month"); const threeYears = await getDashboardStats({ clientId, startDate: "2023-01-01T00:00:00.000Z", endDate: "2026-01-01T23:59:59.999Z", }); expect(threeYears.charts.volumeGranularity).toBe("quarter"); const fiveYears = await getDashboardStats({ clientId, startDate: "2021-01-01T00:00:00.000Z", endDate: "2026-01-01T23:59:59.999Z", }); expect(fiveYears.charts.volumeGranularity).toBe("year"); }); it("should fill gaps with zero-count buckets so the chart has no missing points", async () => { const clientId = "client-dash-789"; await getClientsConfigCollection().insertOne({ clientId, timezone: "UTC", products: {}, } as any); // Sparse data: only two of the day's hours have calls. await getCallsCollection().insertMany([ { clientId, status: "completed", callLength: 60, createdAt: new Date("2026-05-01T09:15:00Z"), }, { clientId, status: "completed", callLength: 90, createdAt: new Date("2026-05-01T18:45:00Z"), }, ] as any); const result = await getDashboardStats({ clientId, startDate: "2026-05-01T00:00:00.000Z", endDate: "2026-05-01T23:59:59.999Z", }); expect(result.charts.volumeGranularity).toBe("hour"); // A full day of hourly buckets, with no gaps, sorted chronologically. expect(result.charts.volumeData).toHaveLength(24); expect(result.charts.volumeData[0].date).toBe("2026-05-01T00:00"); expect(result.charts.volumeData[23].date).toBe("2026-05-01T23:00"); const populated = result.charts.volumeData.filter((d) => d.completed > 0); expect(populated).toHaveLength(2); expect(populated.map((d) => d.date)).toEqual([ "2026-05-01T09:00", "2026-05-01T18:00", ]); const empty = result.charts.volumeData.filter((d) => d.completed === 0); expect(empty).toHaveLength(22); }); it("should count in-progress and answered as inCallCount, not completed", async () => { const clientId = "client-dash-in-call"; await getClientsConfigCollection().insertOne({ clientId, timezone: "UTC", products: {}, } as any); await getCallsCollection().insertMany([ { clientId, status: "in-progress", callLength: 30, createdAt: new Date("2026-05-10T10:00:00Z"), }, { clientId, status: "answered", callLength: 45, createdAt: new Date("2026-05-10T11:00:00Z"), }, { clientId, status: "completed", callLength: 120, createdAt: new Date("2026-05-10T12:00:00Z"), }, ] as any); const result = await getDashboardStats({ clientId, startDate: "2026-05-01T00:00:00.000Z", endDate: "2026-05-31T23:59:59.999Z", }); expect(result.kpis.inCallCount).toBe(2); expect(result.kpis.completedCount).toBe(1); expect(result.kpis.totalCalls).toBe(3); expect(result.kpis.avgDurationSeconds).toBe(120); expect(result.kpis.timeSavedMinutes).toBe(2); }); it("should not count physical busy status as inCall or answered duration", async () => { const clientId = "client-dash-busy"; await getClientsConfigCollection().insertOne({ clientId, timezone: "UTC", products: {}, } as any); await getCallsCollection().insertMany([ { clientId, status: "busy", callLength: 60, createdAt: new Date("2026-05-10T10:00:00Z"), }, { clientId, status: "completed", callLength: 90, createdAt: new Date("2026-05-10T11:00:00Z"), }, ] as any); const result = await getDashboardStats({ clientId, startDate: "2026-05-01T00:00:00.000Z", endDate: "2026-05-31T23:59:59.999Z", }); expect(result.kpis.inCallCount).toBe(0); expect(result.kpis.completedCount).toBe(1); expect(result.kpis.totalCalls).toBe(1); expect(result.kpis.avgDurationSeconds).toBe(90); expect(result.kpis.timeSavedMinutes).toBe(2); }); });