using QuestionsApi.Models; using QuestionsApi.Services; using Xunit; namespace QuestionsApi.Tests; public sealed class ExamSimulationTests { [Fact] public void DayOneFormHasOfficialItemAndBlockCounts() { Assert.Equal(232, ExamSimulationStore.BlockSizes.Sum()); Assert.Equal(12, ExamSimulationStore.BlockSizes.Length); Assert.All(ExamSimulationStore.BlockSizes, size => Assert.InRange(size, 18, 20)); } [Fact] public void BlueprintTargetsFillTheCompleteForm() { var targets = ExamSimulationStore.BuildTargets(); Assert.Equal(ExamSimulationStore.RequiredQuestions, targets.Values.Sum()); Assert.Equal(14, targets.Count); Assert.All(targets.Values, count => Assert.True(count > 0)); } [Fact] public void MoreCorrectAnswersProduceAHigherPrediction() { var items = BuildItems(100); var questions = BuildQuestions(100); var allCorrect = items.ToDictionary( item => item.QuestionExternalId, item => new ExamSimulationResponse { QuestionExternalId = item.QuestionExternalId, ChoiceId = "A" }); var allWrong = items.ToDictionary( item => item.QuestionExternalId, item => new ExamSimulationResponse { QuestionExternalId = item.QuestionExternalId, ChoiceId = "B" }); var passing = ExamSimulationScoring.Calculate(items, allCorrect, questions); var failing = ExamSimulationScoring.Calculate(items, allWrong, questions); Assert.True(passing.PredictedScore > failing.PredictedScore); Assert.True(passing.PassProbability > failing.PassProbability); Assert.Equal(100, passing.Correct); Assert.Equal(0, failing.Correct); } [Fact] public void SeenItemsAreReportedAndCarryLessScoringWeight() { var questions = BuildQuestions(20); var unseen = BuildItems(20); var seen = BuildItems(20, wasSeen: true); var responses = unseen.ToDictionary( item => item.QuestionExternalId, item => new ExamSimulationResponse { QuestionExternalId = item.QuestionExternalId, ChoiceId = "A" }); var unseenScore = ExamSimulationScoring.Calculate(unseen, responses, questions); var seenScore = ExamSimulationScoring.Calculate(seen, responses, questions); Assert.Equal(0, unseenScore.SeenCount); Assert.Equal(20, seenScore.SeenCount); Assert.True(unseenScore.AbilityElo > seenScore.AbilityElo); } [Theory] [InlineData("Cardiovascular System", "Cardiovascular")] [InlineData("Professionalism and Ethics", "Social Sciences")] [InlineData("Endocrine, Diabetes & Metabolism", "Endocrine")] public void QuestionsMapToExpectedBlueprintCategory(string system, string expected) { Assert.Equal(expected, ExamSimulationStore.BlueprintCategory(new Question { System = system })); } [Fact] public void HistorySummarySupportsScoredAndUnscoredStates() { var active = ExamSimulationStore.ToHistorySummary(new ExamSimulation { State = ExamSimulationStates.BlockActive, CurrentBlock = 3, Items = BuildItems(20) }); var completed = ExamSimulationStore.ToHistorySummary(new ExamSimulation { State = ExamSimulationStates.Completed, IsSample = true, Items = BuildItems(20), Score = new ExamSimulationScore { PredictedScore = 221, RangeLow = 204, RangeHigh = 238, Correct = 15, Total = 20 } }); Assert.Null(active.PredictedScore); Assert.Equal(3, active.CurrentBlock); Assert.Equal(221, completed.PredictedScore); Assert.Equal(15, completed.Correct); Assert.True(completed.IsSample); } private static List BuildItems(int count, bool wasSeen = false) => Enumerable.Range(0, count).Select(index => new ExamSimulationItem { QuestionExternalId = $"question-{index}", QuestionElo = 1000, WasSeen = wasSeen, BlueprintCategory = "Cardiovascular" }).ToList(); private static Dictionary BuildQuestions(int count) => Enumerable.Range(0, count).ToDictionary( index => $"question-{index}", index => new Question { ExternalId = $"question-{index}", CorrectChoiceId = "A" }); }