Files
jobtrackingapp/JobTrackerApi.Tests/StageAnalyticsTests.cs
cesnimda 45cbc8b1ab feat: time-in-stage analytics + pipeline-driven funnel
- New pure StageAnalytics.TimeInStage: median days jobs have spent in
  each active pipeline stage (entry time from the last StatusChanged
  event into that stage, else applied date). Closed/success stages
  excluded since 'how long stuck' only applies to actionable stages.
- analytics-overview now derives the funnel from JobPipeline (includes
  the previously-omitted Waiting stage, normalizes legacy spellings) and
  returns TimeInStage.
- 4 unit tests; full backend suite green (124).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 03:40:41 +02:00

63 lines
1.9 KiB
C#

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<StageOccupancy>(), Now));
}