using JobTrackerApi.Services; using Xunit; namespace JobTrackerApi.Tests; public sealed class StageAnalyticsTests { private static readonly DateTime Now = new(2026, 7, 1, 0, 0, 0, DateTimeKind.Utc); [Fact] public void Computes_median_days_per_active_stage() { var jobs = new[] { new StageOccupancy("Applied", Now.AddDays(-10)), new StageOccupancy("Applied", Now.AddDays(-20)), new StageOccupancy("Applied", Now.AddDays(-30)), new StageOccupancy("Interview", Now.AddDays(-4)), }; var result = StageAnalytics.TimeInStage(jobs, Now); var applied = Assert.Single(result, p => p.Stage == "Applied"); Assert.Equal(20, applied.MedianDays); Assert.Equal(3, applied.Count); var interview = Assert.Single(result, p => p.Stage == "Interview"); Assert.Equal(4, interview.MedianDays); } [Fact] public void Excludes_closed_and_success_stages() { var jobs = new[] { new StageOccupancy("Offer", Now.AddDays(-5)), new StageOccupancy("Rejected", Now.AddDays(-5)), new StageOccupancy("Ghosted", Now.AddDays(-5)), }; Assert.Empty(StageAnalytics.TimeInStage(jobs, Now)); } [Fact] public void Normalizes_legacy_status_and_orders_by_pipeline() { var jobs = new[] { new StageOccupancy("Interviewing", Now.AddDays(-3)), new StageOccupancy("Applied", Now.AddDays(-1)), new StageOccupancy("Waiting", Now.AddDays(-2)), }; var result = StageAnalytics.TimeInStage(jobs, Now); Assert.Equal(new[] { "Applied", "Waiting", "Interview" }, result.Select(p => p.Stage).ToArray()); } [Fact] public void Empty_input_returns_empty() => Assert.Empty(StageAnalytics.TimeInStage(Array.Empty(), Now)); }