using QuestionsApi.Models; using QuestionsApi.Services; using Xunit; namespace QuestionsApi.Tests; public sealed class DashboardTests { [Fact] public void PercentageRoundsToNearestWholeNumber() { Assert.Equal(67, DashboardStore.Percentage(2, 3)); Assert.Equal(0, DashboardStore.Percentage(0, 0)); } [Fact] public void StreakIncludesYesterdayWhenUserHasNotStudiedToday() { var today = new DateTime(2026, 7, 18, 0, 0, 0, DateTimeKind.Utc); var dates = new[] { today.AddDays(-1), today.AddDays(-2), today.AddDays(-3), today.AddDays(-5) }; Assert.Equal(3, DashboardStore.CalculateCurrentStreak(dates, today)); } [Fact] public void StreakStopsAtFirstMissingDay() { var today = new DateTime(2026, 7, 18, 0, 0, 0, DateTimeKind.Utc); var dates = new[] { today, today.AddDays(-1), today.AddDays(-3) }; Assert.Equal(2, DashboardStore.CalculateCurrentStreak(dates, today)); } [Fact] public void SystemPerformanceReturnsEveryOfficialSystemInOrder() { var attempts = new[] { Attempt("cardio-1", true), Attempt("cardio-2", false), Attempt("ethics-1", true), Attempt("ignored", true), Attempt("skipped-cardio", false, true) }; var questions = new Dictionary { ["cardio-1"] = Question("Cardiology"), ["cardio-2"] = Question("Cardiovascular System"), ["ethics-1"] = Question("Medical Ethics"), ["ignored"] = Question("Unmapped Specialty"), ["skipped-cardio"] = Question("Cardiology") }; var result = DashboardStore.BuildSystemPerformance(attempts, questions); Assert.Equal(14, result.Count); Assert.Equal(DashboardStore.Step3Systems, result.Select(value => value.System)); var cardiovascular = result.Single(value => value.System == "Cardiovascular System"); Assert.Equal(1, cardiovascular.Correct); Assert.Equal(2, cardiovascular.Total); Assert.Equal(50, cardiovascular.Accuracy); var social = result.Single(value => value.System.StartsWith("Social Sciences")); Assert.Equal(1, social.Correct); Assert.Equal(1, social.Total); Assert.All(result.Where(value => value.System != cardiovascular.System && value.System != social.System), value => Assert.Equal(0, value.Total)); } [Theory] [InlineData("Preventive Medicine", "Human Development")] [InlineData("Infectious Disease", "Immune System, Blood & Lymphoreticular System, and Multisystem Processes/Disorders")] [InlineData("Dermatology", "Skin & Subcutaneous Tissue")] [InlineData("Pulmonary", "Respiratory System")] [InlineData("Pregnancy", "Pregnancy/Childbirth & Female Reproductive System & Breast")] [InlineData("Biostatistics", "Biostatistics & Epidemiology/Population Health & Interpretation of the Medical Literature")] [InlineData("Medical Ethics", "Social Sciences: Communication Skills/Ethics/Patient Safety")] public void SystemAliasesMapToOfficialCategories(string source, string expected) { Assert.Equal(expected, DashboardStore.NormalizeStep3System(source)); } [Fact] public void TrendCarriesRatingForwardAndLeavesInactiveAccuracyAsGapData() { var today = new DateTime(2026, 7, 25, 0, 0, 0, DateTimeKind.Utc); var baselineDate = today.AddDays(-2).AddHours(8); var attempts = new[] { Attempt("one", true, createdUtc: today.AddDays(-2).AddHours(9), rating: 1018, ratingUtc: today.AddDays(-2).AddHours(9)), Attempt("two", false, createdUtc: today.AddDays(-2).AddHours(10), rating: 1002, ratingUtc: today.AddDays(-2).AddHours(10)), Attempt("three", true, createdUtc: today.AddHours(11), rating: 1020, ratingUtc: today.AddHours(11)), Attempt("skip", false, true, today.AddDays(-1).AddHours(4), 990, today.AddDays(-1).AddHours(4)) }; var rating = new UserRating { OverallElo = 1020, HistoryBaselineElo = 1000, HistoryStartedUtc = baselineDate }; var result = DashboardStore.BuildPerformanceTrend(attempts, rating, today); Assert.Equal(30, result.Count); Assert.Null(result[^4].QuestionBankRating); Assert.Equal(1002, result[^3].QuestionBankRating); Assert.Equal(50, result[^3].Accuracy); Assert.Equal(2, result[^3].Total); Assert.Equal(990, result[^2].QuestionBankRating); Assert.Equal(0, result[^2].Total); Assert.Equal(1020, result[^1].QuestionBankRating); Assert.Equal(100, result[^1].Accuracy); } private static QuestionAttempt Attempt( string questionId, bool correct, bool skip = false, DateTime? createdUtc = null, int? rating = null, DateTime? ratingUtc = null) => new() { QuestionExternalId = questionId, IsCorrect = correct, IsSkip = skip, CreatedUtc = createdUtc ?? DateTime.UtcNow, UserOverallEloAfter = rating, RatingAppliedUtc = ratingUtc }; private static Question Question(string system) => new() { ExternalId = system, System = system }; }