using QuestionsApi.Models; using QuestionsApi.Services; using Xunit; namespace QuestionsApi.Tests; public sealed class DailyMissionPolicyTests { private static readonly DateTime Now = new(2026, 8, 1, 12, 0, 0, DateTimeKind.Utc); [Fact] public void DueReviewsArePrioritizedAndCappedAtTen() { var candidates = DailyMissionStore.BuildCandidates(Dashboard(17), Questions(), Now); Assert.Equal(DailyMissionTypes.DueReviews, candidates[0].Type); Assert.Equal(10, candidates[0].Target); } [Fact] public void NoReviewsFallsBackToCoverageGaps() { var candidates = DailyMissionStore.BuildCandidates(Dashboard(0), Questions(), Now); Assert.DoesNotContain(candidates, value => value.Type == DailyMissionTypes.DueReviews); Assert.True(candidates.Count >= 3); Assert.Contains(candidates.Take(3), value => value.Type == DailyMissionTypes.DifficultyFourCorrect); Assert.All(candidates.Where(value => value.Type == DailyMissionTypes.WeakSystemCorrect), value => Assert.Equal(3, value.Target)); } [Fact] public void HardMissionRequiresThreeEligiblePublishedQuestions() { var questions = Questions(); questions.RemoveAll(value => value.Step3System == Step3SystemCatalog.All[1] && value.Difficulty >= 4); var candidates = DailyMissionStore.BuildCandidates(Dashboard(0), questions, Now); Assert.DoesNotContain(candidates, value => value.Type == DailyMissionTypes.DifficultyFourCorrect && value.Step3System == Step3SystemCatalog.All[1]); } [Fact] public void QuestionProgressRequiresCorrectEligibleActivityAfterMissionStart() { var system = Step3SystemCatalog.All[0]; var mission = new DailyMission { Type = DailyMissionTypes.DifficultyFourCorrect, Step3System = system, StartsUtc = Now }; var question = new Question { Step3System = system, Difficulty = 4 }; Assert.True(DailyMissionStore.QualifiesQuestion(mission, new QuestionAttempt { IsCorrect = true, CreatedUtc = Now }, question)); Assert.False(DailyMissionStore.QualifiesQuestion(mission, new QuestionAttempt { IsCorrect = false, CreatedUtc = Now }, question)); Assert.False(DailyMissionStore.QualifiesQuestion(mission, new QuestionAttempt { IsCorrect = true, IsSkip = true, CreatedUtc = Now }, question)); Assert.False(DailyMissionStore.QualifiesQuestion(mission, new QuestionAttempt { IsCorrect = true, CreatedUtc = Now.AddSeconds(-1) }, question)); question.Difficulty = 3; Assert.False(DailyMissionStore.QualifiesQuestion(mission, new QuestionAttempt { IsCorrect = true, CreatedUtc = Now }, question)); } private static DashboardResponse Dashboard(int pending) => new() { MissedQuestionCount = pending, Systems = Step3SystemCatalog.All.Take(4).Select((system, index) => new DashboardSystemPerformance { System = system, MasteryState = index == 0 ? MasteryStates.Unseen : MasteryStates.Learning, MasteryScore = index * 10, DistinctQuestions = index, Difficulty4PlusSuccessful = index }).ToList() }; private static List Questions() => Step3SystemCatalog.All.Take(4) .SelectMany(system => Enumerable.Range(0, 6).Select(index => new Question { ExternalId = $"{system}-{index}", Step3System = system, Difficulty = index < 3 ? 4 : 2, IsPublished = true })).ToList(); }