45cbc8b1ab
- 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>
63 lines
1.9 KiB
C#
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));
|
|
}
|