/** * YouTube Analytics Helpers Unit Tests * * Tests comparison, trend, and ROI calculation functions * used by the analytics API route. */ import { describe, it, expect } from 'vitest' import { calculateComparison, calculateTrends, calculateROI, } from '@/lib/youtube/analytics-helpers' import type { VideoWithPerformance } from '@/lib/youtube/analytics-helpers' const mockVideos: VideoWithPerformance[] = [ { id: 1, title: 'Video A', performance: { views: 1000, likes: 50, ctr: 5.2, watchTimeMinutes: 300 }, costs: { estimatedProductionCost: 100, estimatedRevenue: 200 }, }, { id: 2, title: 'Video B', performance: { views: 2000, likes: 80, ctr: 3.1, watchTimeMinutes: 600 }, costs: { estimatedProductionCost: 150, estimatedRevenue: 100 }, }, { id: 3, title: 'Video C', performance: { views: 500, likes: 20, ctr: 7.0, watchTimeMinutes: 150 }, costs: { estimatedProductionCost: 50, estimatedRevenue: 75 }, }, ] describe('Analytics Helpers', () => { describe('calculateComparison', () => { it('should return comparison metrics for all videos', () => { const result = calculateComparison(mockVideos, 'views') expect(result).toHaveLength(3) expect(result[0]).toEqual({ videoId: 1, title: 'Video A', value: 1000 }) expect(result[1]).toEqual({ videoId: 2, title: 'Video B', value: 2000 }) expect(result[2]).toEqual({ videoId: 3, title: 'Video C', value: 500 }) }) it('should handle different metrics', () => { const result = calculateComparison(mockVideos, 'likes') expect(result[0].value).toBe(50) expect(result[1].value).toBe(80) expect(result[2].value).toBe(20) }) it('should return 0 for missing metric values', () => { const sparse: VideoWithPerformance[] = [ { id: 10, title: 'Sparse', performance: {} }, ] const result = calculateComparison(sparse, 'impressions') expect(result).toHaveLength(1) expect(result[0].value).toBe(0) }) it('should return an empty array for empty input', () => { const result = calculateComparison([], 'views') expect(result).toEqual([]) }) }) describe('calculateTrends', () => { it('should detect an upward trend when latest exceeds average', () => { const result = calculateTrends(mockVideos, 'views') expect(result.trend).toBeDefined() expect(result.average).toBeDefined() expect(result.latest).toBeDefined() expect(result.growth).toBeDefined() expect(result.min).toBeDefined() expect(result.max).toBeDefined() }) it('should return insufficient_data for fewer than 2 videos', () => { const result = calculateTrends([mockVideos[0]], 'views') expect(result.trend).toBe('insufficient_data') expect(result.growth).toBe(0) expect(result.average).toBeUndefined() }) it('should return insufficient_data for an empty array', () => { const result = calculateTrends([], 'views') expect(result.trend).toBe('insufficient_data') expect(result.growth).toBe(0) }) it('should compute correct average and min/max', () => { const result = calculateTrends(mockVideos, 'views') // Values sorted: 500, 1000, 2000 // avg = (500 + 1000 + 2000) / 3 = ~1166.67 const expectedAvg = (500 + 1000 + 2000) / 3 expect(result.average).toBeCloseTo(expectedAvg) expect(result.min).toBe(500) expect(result.max).toBe(2000) }) it('should calculate growth percentage relative to average', () => { const result = calculateTrends(mockVideos, 'views') const expectedAvg = (500 + 1000 + 2000) / 3 const expectedGrowth = ((2000 - expectedAvg) / expectedAvg) * 100 expect(result.growth).toBeCloseTo(expectedGrowth) }) it('should return stable trend when latest equals average', () => { const equal: VideoWithPerformance[] = [ { id: 1, title: 'A', performance: { views: 100 } }, { id: 2, title: 'B', performance: { views: 100 } }, ] const result = calculateTrends(equal, 'views') expect(result.trend).toBe('stable') expect(result.growth).toBe(0) }) it('should handle zero values gracefully', () => { const zeros: VideoWithPerformance[] = [ { id: 1, title: 'A', performance: { views: 0 } }, { id: 2, title: 'B', performance: { views: 0 } }, ] const result = calculateTrends(zeros, 'views') expect(result.trend).toBe('stable') expect(result.growth).toBe(0) expect(result.average).toBe(0) }) }) describe('calculateROI', () => { it('should calculate ROI for videos with cost data', () => { const result = calculateROI(mockVideos) expect(result).toHaveLength(3) }) it('should compute correct ROI percentage', () => { const result = calculateROI(mockVideos) const videoA = result.find((r) => r.videoId === 1)! // ROI = (200 - 100) / 100 * 100 = 100% expect(videoA.roi).toBe(100) expect(videoA.cost).toBe(100) expect(videoA.revenue).toBe(200) }) it('should compute correct cost-per-view', () => { const result = calculateROI(mockVideos) const videoA = result.find((r) => r.videoId === 1)! // CPV = 100 / 1000 = 0.1 expect(videoA.cpv).toBeCloseTo(0.1) }) it('should compute correct revenue-per-view', () => { const result = calculateROI(mockVideos) const videoB = result.find((r) => r.videoId === 2)! // revenuePerView = 100 / 2000 = 0.05 expect(videoB.revenuePerView).toBeCloseTo(0.05) }) it('should handle negative ROI', () => { const result = calculateROI(mockVideos) const videoB = result.find((r) => r.videoId === 2)! // ROI = (100 - 150) / 150 * 100 = -33.33% expect(videoB.roi).toBeCloseTo(-33.33, 1) }) it('should skip videos without cost data', () => { const noCost: VideoWithPerformance[] = [ { id: 10, title: 'No Cost', performance: { views: 500 } }, ] const result = calculateROI(noCost) expect(result).toHaveLength(0) }) it('should skip videos with zero production cost', () => { const zeroCost: VideoWithPerformance[] = [ { id: 10, title: 'Zero Cost', performance: { views: 500 }, costs: { estimatedProductionCost: 0, estimatedRevenue: 100 }, }, ] const result = calculateROI(zeroCost) expect(result).toHaveLength(0) }) it('should handle videos with zero views', () => { const zeroViews: VideoWithPerformance[] = [ { id: 10, title: 'Zero Views', performance: { views: 0 }, costs: { estimatedProductionCost: 100, estimatedRevenue: 0 }, }, ] const result = calculateROI(zeroViews) expect(result).toHaveLength(1) expect(result[0].cpv).toBe(0) expect(result[0].revenuePerView).toBe(0) }) it('should handle missing revenue', () => { const noRevenue: VideoWithPerformance[] = [ { id: 10, title: 'No Revenue', performance: { views: 100 }, costs: { estimatedProductionCost: 50 }, }, ] const result = calculateROI(noRevenue) expect(result).toHaveLength(1) expect(result[0].revenue).toBe(0) // ROI = (0 - 50) / 50 * 100 = -100% expect(result[0].roi).toBe(-100) }) }) })