using AIStudio.Assistants.BeyondFactsLab; namespace AIStudio.Tests.BeyondFactsLab; /// /// Checks how the BeyondFacts Lab reads a work state which a model wrote as Markdown. /// /// /// The keys of the sections are stored in every work state, so they are pinned here as well. /// [TestFixture] public sealed class BflMarkdownSectionsTests { [Test] public void TheSectionsKeepTheirKeys() { Assert.That(BflWorkStateSections.SECTIONS.Select(section => $"{section.Step}/{section.Key}"), Is.EqualTo(new[] { "FRAMEWORK/FORMAT", "FRAMEWORK/AUDIENCE", "FRAMEWORK/SPEAKING_TIME", "FRAMEWORK/PARTICIPANTS", "FRAMEWORK/CONFLICTS", "FRAMEWORK/OPEN_POINTS", "KNOWLEDGE/ESTABLISHED", "KNOWLEDGE/UNCERTAINTIES", "KNOWLEDGE/WEAK_POINTS", "KNOWLEDGE/CONTROVERSIES", "KNOWLEDGE/EXPERTISE_LIMITS", "KNOWLEDGE/KEY_MESSAGES", "KNOWLEDGE/OPEN_POINTS", "GOALS_AND_ROLE/GOAL", "GOALS_AND_ROLE/ROLE", "GOALS_AND_ROLE/REASONS", "GOALS_AND_ROLE/CONSEQUENCES", "GOALS_AND_ROLE/OPEN_POINTS", "VALUES_AND_PERSPECTIVES/VALUES", "VALUES_AND_PERSPECTIVES/VALUE_CONFLICTS", "VALUES_AND_PERSPECTIVES/PERSONAL_INVOLVEMENT", "VALUES_AND_PERSPECTIVES/PERSPECTIVES", "VALUES_AND_PERSPECTIVES/CONSEQUENCES", "VALUES_AND_PERSPECTIVES/OPEN_POINTS", "STRATEGY/KEY_MESSAGES", "STRATEGY/STRUCTURE", "STRATEGY/LANGUAGE", "STRATEGY/UNCERTAINTY", "STRATEGY/FRAMING", "STRATEGY/PARTICIPANTS", "STRATEGY/COUNTERARGUMENTS", "STRATEGY/WORRIES", "STRATEGY/OPEN_POINTS", "TEST_SITUATION/STRENGTHS", "TEST_SITUATION/PRACTICE", "TEST_SITUATION/HARD_QUESTIONS", "TEST_SITUATION/POSITION", "TEST_SITUATION/OPEN_POINTS", "RESULTS/CHANGED", "RESULTS/KEPT", "RESULTS/LEFT_OPEN", "RESULTS/NEXT_STEPS", })); } [Test] public void EveryStepHasSectionsWithUniqueKeysAndTitles() { foreach (var step in BflStep.All) { var sections = BflWorkStateSections.ForStep(step); using (Assert.EnterMultipleScope()) { Assert.That(sections, Is.Not.Empty, step.ToString()); Assert.That(sections.Select(section => section.Key), Is.Unique, step.ToString()); Assert.That(sections.Select(section => section.Title), Is.Unique, step.ToString()); Assert.That(sections.Select(section => section.Title), Has.None.EqualTo(string.Empty), step.ToString()); Assert.That(sections.Select(section => section.Description), Has.All.Not.Empty, step.ToString()); } } } [Test] public void OnlyThePersonalInvolvementIsPersonal() { Assert.That(BflWorkStateSections.SECTIONS.Where(section => section.IsPersonalDisclosure).Select(section => section.Key), Is.EqualTo(new[] { "PERSONAL_INVOLVEMENT" })); } [TestCase("## [GOAL] Ziel", TestName = "AHeadingOfTheSecondLevelIsASection")] [TestCase("### [GOAL] Ziel", TestName = "AHeadingOfAnotherLevelIsASection")] [TestCase("##[GOAL]", TestName = "AHeadingWithoutSpaceAndTitleIsASection")] [TestCase("## **[GOAL] Ziel**", TestName = "ABoldHeadingIsASection")] [TestCase("## [ goal ] Ziel", TestName = "TheKeyIgnoresCaseAndSpaces")] [TestCase(" ## [GOAL] Ziel", TestName = "AnIndentedHeadingIsASection")] public void TheHeadingOfASectionMayVary(string heading) { var answer = Read($"{heading}\nWirksam informieren.\n\n## [ROLE]\nEhrliche Maklerin\n## [REASONS]\n## [CONSEQUENCES]\nKurz fassen.\n## [OPEN_POINTS]\n"); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Is.Empty); Assert.That(answer.Sections["GOAL"], Is.EqualTo("Wirksam informieren.")); Assert.That(answer.Sections["ROLE"], Is.EqualTo("Ehrliche Maklerin")); Assert.That(answer.Sections["REASONS"], Is.Empty); Assert.That(answer.Sections["OPEN_POINTS"], Is.Empty); } } [Test] public void TextBeforeTheFirstHeadingAndACodeBlockAroundTheAnswerAreLeftOut() { var answer = Read("Hier ist der Arbeitsstand:\n```markdown\n## [GOAL] Ziel\nInformieren\n- Punkt eins\n- Punkt zwei\n## [ROLE] Rolle\nMaklerin\n## [REASONS] Gründe\n## [CONSEQUENCES] Folgen\n## [OPEN_POINTS] Offen\nKeine\n```"); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Is.Empty); Assert.That(answer.Sections["GOAL"], Is.EqualTo("Informieren\n- Punkt eins\n- Punkt zwei")); Assert.That(answer.Sections["OPEN_POINTS"], Is.EqualTo("Keine")); } } [Test] public void AHeadingWithoutKeyBelongsToTheTextOfItsSection() { var answer = Read("## [GOAL] Ziel\n### Kurz gesagt\nInformieren\n## [ROLE]\n## [REASONS]\n## [CONSEQUENCES]\n## [OPEN_POINTS]"); Assert.That(answer.Sections["GOAL"], Is.EqualTo("### Kurz gesagt\nInformieren")); } [Test] public void AMissingSectionIsAProblem() { var answer = Read("## [GOAL]\nInformieren\n## [ROLE]\n## [CONSEQUENCES]\n## [OPEN_POINTS]"); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Has.Count.EqualTo(1)); Assert.That(answer.Problems[0], Does.Contain("[REASONS]")); Assert.That(answer.Sections.Keys, Is.EquivalentTo(new[] { "GOAL", "ROLE", "CONSEQUENCES", "OPEN_POINTS" })); } } [Test] public void ADuplicateSectionIsAProblemAndTheFirstOneCounts() { var answer = Read("## [GOAL]\nErstens\n## [GOAL]\nZweitens\n## [ROLE]\n## [REASONS]\n## [CONSEQUENCES]\n## [OPEN_POINTS]"); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Has.Count.EqualTo(1)); Assert.That(answer.Problems[0], Does.Contain("[GOAL]")); Assert.That(answer.Sections["GOAL"], Is.EqualTo("Erstens")); } } [Test] public void AnUnknownSectionIsAProblem() { var answer = Read("## [GOAL]\n## [ROLE]\n## [REASONS]\n## [CONSEQUENCES]\n## [OPEN_POINTS]\n## [AUDIENCE] Publikum\nStudierende"); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Has.Count.EqualTo(1)); Assert.That(answer.Problems[0], Does.Contain("[AUDIENCE]")); Assert.That(answer.Sections.ContainsKey("AUDIENCE"), Is.False); } } [Test] public void AnAnswerWithoutSectionsIsAProblem() { var answer = Read("Ich habe leider keine Angaben."); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Has.Count.EqualTo(1)); Assert.That(answer.Sections, Is.Empty); } } [Test] public void ASuggestionForAnEmptyTextFieldIsRead() { var form = new BflStepFormData(); form.Set(BflFieldCatalog.FORMAT, "Podium"); var answer = BflMarkdownSections.Read(FrameworkWithSuggestions("## [FIELD:audience] Zielgruppe\nSchüler*innen\n## [field:EVENT_LINK]\nhttps://example.org/podium"), BflStep.FRAMEWORK, form); using (Assert.EnterMultipleScope()) { Assert.That(answer.Problems, Is.Empty); Assert.That(answer.FieldSuggestions, Is.EqualTo(new Dictionary { [BflFieldCatalog.AUDIENCE] = "Schüler*innen", [BflFieldCatalog.EVENT_LINK] = "https://example.org/podium", })); } } [TestCase("## [FIELD:format]\nInterview", TestName = "AFieldWhichHoldsAValueGetsNoSuggestion")] [TestCase("## [FIELD:unknown_field]\nWert", TestName = "AnUnknownFieldGetsNoSuggestion")] [TestCase("## [FIELD:core_theses]\nThese", TestName = "AFieldOfAnotherStepGetsNoSuggestion")] [TestCase("## [FIELD:event_link]\nexample.org", TestName = "ALinkWithoutSchemeIsNoSuggestion")] [TestCase("## [FIELD:event_link]\njavascript:alert(1)", TestName = "ALinkOfAnotherSchemeIsNoSuggestion")] [TestCase("## [FIELD:audience]\nErste Zeile\nZweite Zeile", TestName = "ASingleLineFieldGetsNoSuggestionWithSeveralLines")] [TestCase("## [FIELD:audience]\n", TestName = "AnEmptySuggestionIsLeftOut")] public void ASuggestionWhichDoesNotFitIsLeftOutWithoutAProblem(string suggestion) { var form = new BflStepFormData(); form.Set(BflFieldCatalog.FORMAT, "Podium"); var answer = BflMarkdownSections.Read(FrameworkWithSuggestions(suggestion), BflStep.FRAMEWORK, form); using (Assert.EnterMultipleScope()) { Assert.That(answer.FieldSuggestions, Is.Empty); Assert.That(answer.Problems, Is.Empty); } } [Test] public void ASuggestionLongerThanItsFieldIsLeftOut() { var answer = BflMarkdownSections.Read(FrameworkWithSuggestions($"## [FIELD:audience]\n{new string('x', BflFieldKind.SINGLE_LINE.MaxLength + 1)}"), BflStep.FRAMEWORK, new BflStepFormData()); Assert.That(answer.FieldSuggestions, Is.Empty); } [Test] public void TheFirstSuggestionForAFieldCounts() { var answer = BflMarkdownSections.Read(FrameworkWithSuggestions("## [FIELD:audience]\nErste\n## [FIELD:audience]\nZweite"), BflStep.FRAMEWORK, new BflStepFormData()); Assert.That(answer.FieldSuggestions[BflFieldCatalog.AUDIENCE], Is.EqualTo("Erste")); } [Test] public void OnlyEmptyFieldsWithATextOrASingleChoiceWhichAreNotPersonalGetSuggestions() { var goals = BflMarkdownSections.SuggestableFields(BflStep.GOALS_AND_ROLE, new BflStepFormData()).Select(field => field.Id); var values = BflMarkdownSections.SuggestableFields(BflStep.VALUES_AND_PERSPECTIVES, new BflStepFormData()).Select(field => field.Id); var test = BflMarkdownSections.SuggestableFields(BflStep.TEST_SITUATION, new BflStepFormData()).Select(field => field.Id); using (Assert.EnterMultipleScope()) { Assert.That(goals, Is.EqualTo(new[] { BflFieldCatalog.GOALS }), "A field with several choices gets no suggestion."); Assert.That(values, Is.EqualTo(new[] { BflFieldCatalog.VALUES_AND_CONCERNS, BflFieldCatalog.OTHER_PERSPECTIVES })); Assert.That(test, Is.EqualTo(new[] { BflFieldCatalog.QUESTION_TYPE, BflFieldCatalog.DIFFICULTY, BflFieldCatalog.TEST_NOTES })); } } [Test] public void ASuggestionForAChoiceNamesItsOptionByLabelOrIdAndKeepsTheId() { var label = BflFieldCatalog.FIELDS.Single(field => field.Id == BflFieldCatalog.DIFFICULTY).Options.Single(option => option.Id == BflFieldCatalog.DIFFICULTY_DEMANDING).Label; string[] suggestions = [label, label.ToUpperInvariant(), $"„{label}“", $"\"{label}\"", $"**{label}**", $"{label}.", BflFieldCatalog.DIFFICULTY_DEMANDING]; using (Assert.EnterMultipleScope()) { foreach (var suggestion in suggestions) { var answer = BflMarkdownSections.Read($"## [FIELD:difficulty]\n{suggestion}", BflStep.TEST_SITUATION, new BflStepFormData()); Assert.That(answer.FieldSuggestions, Is.EqualTo(new Dictionary { [BflFieldCatalog.DIFFICULTY] = BflFieldCatalog.DIFFICULTY_DEMANDING }), suggestion); } } } [TestCase("## [FIELD:difficulty]\nSomewhat demanding", TestName = "ASuggestionWhichNamesNoOptionIsLeftOut")] [TestCase("## [FIELD:difficulty]\ndifficulty_moderate, difficulty_demanding", TestName = "ASuggestionWithSeveralOptionsIsLeftOut")] [TestCase("## [FIELD:goal_directions]\ngoal_inform", TestName = "AFieldWithSeveralChoicesGetsNoSuggestion")] public void ASuggestionForAChoiceWhichDoesNotFitIsLeftOut(string suggestion) { var step = suggestion.Contains("goal_", StringComparison.Ordinal) ? BflStep.GOALS_AND_ROLE : BflStep.TEST_SITUATION; var answer = BflMarkdownSections.Read(suggestion, step, new BflStepFormData()); Assert.That(answer.FieldSuggestions, Is.Empty); } [Test] public void AChoiceWhichHoldsAnOptionGetsNoSuggestion() { var form = new BflStepFormData(); form.Set(BflFieldCatalog.DIFFICULTY, BflFieldCatalog.DIFFICULTY_MODERATE); var answer = BflMarkdownSections.Read($"## [FIELD:difficulty]\n{BflFieldCatalog.DIFFICULTY_DEMANDING}", BflStep.TEST_SITUATION, form); Assert.That(answer.FieldSuggestions, Is.Empty); } private static BflWorkStateAnswer Read(string answer) => BflMarkdownSections.Read(answer, BflStep.GOALS_AND_ROLE, new BflStepFormData()); private static string FrameworkWithSuggestions(string suggestions) => $"## [FORMAT]\n## [AUDIENCE]\n## [SPEAKING_TIME]\n## [PARTICIPANTS]\n## [CONFLICTS]\n## [OPEN_POINTS]\n{suggestions}"; }