import { describe, it, expect } from 'vitest'; import { computeDealHealth } from '@/lib/services/deal-health'; const daysAgo = (n: number): string => new Date(Date.now() - n * 86_400_000).toISOString(); describe('computeDealHealth', () => { it('returns baseline 50 + warm pulse when no signals fire', () => { const r = computeDealHealth({ pipelineStage: 'enquiry' }); expect(r.score).toBe(50); expect(r.pulse).toBe('warm'); expect(r.signals).toEqual([]); }); it('returns neutral baseline when interest is closed/won', () => { const r = computeDealHealth({ pipelineStage: 'contract', outcome: 'won', dateLastContact: daysAgo(2), }); expect(r.score).toBe(50); expect(r.signals).toEqual([]); }); it('returns neutral baseline when interest is archived', () => { const r = computeDealHealth({ pipelineStage: 'eoi', archivedAt: daysAgo(10), dateLastContact: daysAgo(2), }); expect(r.signals).toEqual([]); }); it('+20 for contact in last 7 days', () => { const r = computeDealHealth({ pipelineStage: 'enquiry', dateLastContact: daysAgo(3), }); const sig = r.signals.find((s) => s.id === 'contact_recent'); expect(sig?.delta).toBe(20); expect(r.score).toBeGreaterThanOrEqual(70); expect(r.pulse).toBe('hot'); }); it('+10 for contact within 14 days', () => { const r = computeDealHealth({ pipelineStage: 'enquiry', dateLastContact: daysAgo(10), }); const sig = r.signals.find((s) => s.id === 'contact_warm'); expect(sig?.delta).toBe(10); }); it('-15 when no contact in 30+ days', () => { const r = computeDealHealth({ pipelineStage: 'qualified', dateLastContact: daysAgo(40), }); const sig = r.signals.find((s) => s.id === 'contact_stale'); expect(sig?.delta).toBe(-15); }); it('-10 when EOI sent >14d ago and still unsigned', () => { const r = computeDealHealth({ pipelineStage: 'eoi', dateEoiSent: daysAgo(20), eoiDocStatus: 'sent', }); const sig = r.signals.find((s) => s.id === 'eoi_awaiting'); expect(sig?.delta).toBe(-10); }); it('does NOT penalize EOI awaiting when sub-status is signed', () => { const r = computeDealHealth({ pipelineStage: 'eoi', dateEoiSent: daysAgo(20), eoiDocStatus: 'signed', }); expect(r.signals.find((s) => s.id === 'eoi_awaiting')).toBeUndefined(); }); it('-10 when reservation signed >21d ago but no deposit', () => { const r = computeDealHealth({ pipelineStage: 'reservation', dateReservationSigned: daysAgo(25), }); const sig = r.signals.find((s) => s.id === 'deposit_pending'); expect(sig?.delta).toBe(-10); }); it('does NOT penalize deposit_pending when deposit already received', () => { const r = computeDealHealth({ pipelineStage: 'deposit_paid', dateReservationSigned: daysAgo(25), dateDepositReceived: daysAgo(2), }); expect(r.signals.find((s) => s.id === 'deposit_pending')).toBeUndefined(); }); it('stage progress bonus caps at +30', () => { const r = computeDealHealth({ pipelineStage: 'contract', contractDocStatus: 'signed', }); const sig = r.signals.find((s) => s.id === 'stage_progress'); expect(sig?.delta).toBeLessThanOrEqual(30); expect(sig?.delta).toBeGreaterThan(0); }); it('-10 for deals stuck pre-EOI for 30+ days', () => { const r = computeDealHealth({ pipelineStage: 'enquiry', dateFirstContact: daysAgo(45), }); const sig = r.signals.find((s) => s.id === 'stuck_top_funnel'); expect(sig?.delta).toBe(-10); }); it('clamps score to [0, 100]', () => { const r = computeDealHealth({ pipelineStage: 'contract', dateLastContact: daysAgo(2), contractDocStatus: 'signed', }); expect(r.score).toBeLessThanOrEqual(100); expect(r.score).toBeGreaterThanOrEqual(0); }); it('rates a fresh contract-signed deal as hot', () => { const r = computeDealHealth({ pipelineStage: 'contract', dateLastContact: daysAgo(1), contractDocStatus: 'signed', }); expect(r.pulse).toBe('hot'); }); it('rates a stalled mid-funnel deal with no recent contact as cold', () => { const r = computeDealHealth({ pipelineStage: 'qualified', dateFirstContact: daysAgo(60), dateLastContact: daysAgo(45), }); expect(r.pulse).toBe('cold'); }); it('+5 active_engagement when recentActivityCount >= 1', () => { const r = computeDealHealth({ pipelineStage: 'enquiry', recentActivityCount: 3, }); const sig = r.signals.find((s) => s.id === 'active_engagement'); expect(sig?.delta).toBe(5); }); it('skips active_engagement when recentActivityCount is null/undefined', () => { const r = computeDealHealth({ pipelineStage: 'enquiry', recentActivityCount: null, }); expect(r.signals.find((s) => s.id === 'active_engagement')).toBeUndefined(); }); it('skips active_engagement when recentActivityCount is 0', () => { const r = computeDealHealth({ pipelineStage: 'enquiry', recentActivityCount: 0, }); expect(r.signals.find((s) => s.id === 'active_engagement')).toBeUndefined(); }); });