fix architecture and write new tests
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
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: 'MARKETPLACE', _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: 'MARKETPLACE', 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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user