7ff2dbb139
Build & Deploy / Build & Push Docker Image (push) Successful in 2m55s
Test / Type Check (all packages) (push) Successful in 54s
Build & Deploy / Deploy to VPS (push) Successful in 4s
Test / API Unit Tests (push) Failing after 48s
Test / Homepage Unit Tests (push) Successful in 43s
Test / Storefront Unit Tests (push) Failing after 40s
Test / Admin Unit Tests (push) Successful in 40s
Test / Dashboard Unit Tests (push) Failing after 42s
Test / API Integration Tests (push) Successful in 1m0s
151 lines
5.4 KiB
TypeScript
151 lines
5.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
|
|
vi.mock('../../lib/prisma', () => ({
|
|
prisma: {
|
|
reservation: {
|
|
count: vi.fn(),
|
|
aggregate: vi.fn(),
|
|
findMany: vi.fn(),
|
|
groupBy: vi.fn(),
|
|
},
|
|
vehicle: {
|
|
count: vi.fn(),
|
|
},
|
|
customer: {
|
|
count: vi.fn(),
|
|
},
|
|
subscription: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
accountingSettings: {
|
|
findUnique: vi.fn(),
|
|
},
|
|
},
|
|
}))
|
|
|
|
vi.mock('../../services/financialReportService', () => ({
|
|
generateFinancialReport: vi.fn(),
|
|
toCsv: vi.fn(),
|
|
}))
|
|
|
|
import { prisma } from '../../lib/prisma'
|
|
import { generateFinancialReport, toCsv } from '../../services/financialReportService'
|
|
import { getDashboard, getReport, getSources, getSummary } from './analytics.service'
|
|
|
|
describe('analytics.service', () => {
|
|
beforeEach(() => {
|
|
vi.clearAllMocks()
|
|
vi.setSystemTime(new Date('2026-06-08T12:00:00.000Z'))
|
|
})
|
|
|
|
it('summarizes reservations, available vehicles, revenue, and customers for a day window', async () => {
|
|
vi.mocked(prisma.reservation.count).mockResolvedValue(7 as never)
|
|
vi.mocked(prisma.vehicle.count).mockResolvedValue(12 as never)
|
|
vi.mocked(prisma.reservation.aggregate).mockResolvedValue({ _sum: { totalAmount: 4800 } } as never)
|
|
vi.mocked(prisma.customer.count).mockResolvedValue(42 as never)
|
|
|
|
const result = await getSummary('company_1', '14d')
|
|
|
|
expect(prisma.reservation.count).toHaveBeenCalledWith({
|
|
where: { companyId: 'company_1', createdAt: { gte: new Date('2026-05-25T12:00:00.000Z') } },
|
|
})
|
|
expect(prisma.vehicle.count).toHaveBeenCalledWith({ where: { companyId: 'company_1', status: 'AVAILABLE' } })
|
|
expect(result).toEqual({
|
|
totalReservations: 7,
|
|
activeVehicles: 12,
|
|
totalRevenue: 4800,
|
|
totalCustomers: 42,
|
|
period: '14d',
|
|
})
|
|
})
|
|
|
|
it('builds dashboard KPIs, percentage deltas, recent reservation cards, source breakdown, and subscription summary', async () => {
|
|
vi.mocked(prisma.reservation.count)
|
|
.mockResolvedValueOnce(10 as never)
|
|
.mockResolvedValueOnce(5 as never)
|
|
vi.mocked(prisma.vehicle.count)
|
|
.mockResolvedValueOnce(4 as never)
|
|
.mockResolvedValueOnce(0 as never)
|
|
vi.mocked(prisma.customer.count)
|
|
.mockResolvedValueOnce(20 as never)
|
|
.mockResolvedValueOnce(25 as never)
|
|
vi.mocked(prisma.reservation.aggregate)
|
|
.mockResolvedValueOnce({ _sum: { totalAmount: 9000 } } as never)
|
|
.mockResolvedValueOnce({ _sum: { totalAmount: 3000 } } as never)
|
|
vi.mocked(prisma.reservation.findMany).mockResolvedValue([
|
|
{
|
|
id: 'reservation_abc12345',
|
|
contractNumber: null,
|
|
customer: { firstName: 'Nora', lastName: 'Driver' },
|
|
vehicle: { make: 'Dacia', model: 'Duster' },
|
|
startDate: new Date('2026-06-10T00:00:00.000Z'),
|
|
endDate: new Date('2026-06-12T00:00:00.000Z'),
|
|
status: 'CONFIRMED',
|
|
totalAmount: 1200,
|
|
},
|
|
] as never)
|
|
vi.mocked(prisma.reservation.groupBy).mockResolvedValue([
|
|
{ source: 'CARPLACE', _count: { id: 3 }, _sum: { totalAmount: 3600 } },
|
|
{ source: 'DIRECT', _count: { id: 2 }, _sum: { totalAmount: null } },
|
|
] as never)
|
|
vi.mocked(prisma.subscription.findUnique).mockResolvedValue({
|
|
status: 'ACTIVE',
|
|
plan: 'PRO',
|
|
trialEndAt: new Date('2026-06-30T00:00:00.000Z'),
|
|
} as never)
|
|
|
|
const result = await getDashboard('company_1')
|
|
|
|
expect(result.kpis).toEqual({
|
|
totalBookings: 10,
|
|
activeVehicles: 4,
|
|
monthlyRevenue: 9000,
|
|
totalCustomers: 20,
|
|
bookingsChange: 100,
|
|
vehiclesChange: 100,
|
|
revenueChange: 200,
|
|
customersChange: -20,
|
|
})
|
|
expect(result.recentReservations).toEqual([expect.objectContaining({
|
|
id: 'reservation_abc12345',
|
|
bookingRef: 'ABC12345',
|
|
customerName: 'Nora Driver',
|
|
vehicleName: 'Dacia Duster',
|
|
status: 'CONFIRMED',
|
|
totalAmount: 1200,
|
|
})])
|
|
expect(result.sourceBreakdown).toEqual([
|
|
{ source: 'CARPLACE', count: 3, revenue: 3600 },
|
|
{ source: 'DIRECT', count: 2, revenue: 0 },
|
|
])
|
|
expect(result.subscription).toEqual({
|
|
status: 'ACTIVE',
|
|
planName: 'PRO',
|
|
trialEndsAt: new Date('2026-06-30T00:00:00.000Z'),
|
|
})
|
|
})
|
|
|
|
it('returns source groups straight from reservation grouping', async () => {
|
|
vi.mocked(prisma.reservation.groupBy).mockResolvedValue([{ source: 'DIRECT', _count: { id: 2 } }] as never)
|
|
|
|
await expect(getSources('company_1')).resolves.toEqual([{ source: 'DIRECT', _count: { id: 2 } }])
|
|
expect(prisma.reservation.groupBy).toHaveBeenCalledWith({ by: ['source'], where: { companyId: 'company_1' }, _count: { id: true } })
|
|
})
|
|
|
|
it('uses explicit report dates and emits CSV only when requested', async () => {
|
|
vi.mocked(prisma.accountingSettings.findUnique).mockResolvedValue({ reportingPeriod: 'ANNUAL' } as never)
|
|
vi.mocked(generateFinancialReport).mockResolvedValue({ rows: [{ label: 'Revenue', amount: 1000 }] } as never)
|
|
vi.mocked(toCsv).mockReturnValue('label,amount\nRevenue,1000')
|
|
|
|
const result = await getReport('company_1', {
|
|
from: '2026-05-01',
|
|
to: '2026-05-31',
|
|
format: 'CSV',
|
|
})
|
|
|
|
expect(generateFinancialReport).toHaveBeenCalledWith('company_1', new Date('2026-05-01'), new Date('2026-05-31'))
|
|
expect(toCsv).toHaveBeenCalledWith([{ label: 'Revenue', amount: 1000 }])
|
|
expect(result.csv).toBe('label,amount\nRevenue,1000')
|
|
})
|
|
})
|