Added sharing a BeyondFacts Lab session with the research team (#1196)

This commit is contained in:
Thorsten Sommer authored and GitHub committed 2026-11-12 15:02:00 +01:00
1 parent a1b493cb13
commit 0885862150
27 files changed
+2852 -39

No files matched your search

@@ -0,0 +1,184 @@
using AIStudio.Assistants.BeyondFactsLab;
namespace AIStudio.Tests.BeyondFactsLab;
/// <summary>
/// Checks which personal details the BeyondFacts Lab removes from a session before the person shares it.
/// </summary>
/// <remarks>
/// The anonymizer follows rules. These tests describe what the rules promise: the forms in which names appear,
/// addresses, paths, and phone numbers, and that a second run changes nothing.
/// </remarks>
[TestFixture]
public sealed class BflAnonymizerTests
{
[TestCase("Jörg Müller hat geantwortet.")]
[TestCase("Joerg Mueller hat geantwortet.")]
[TestCase("Jorg Muller hat geantwortet.")]
[TestCase("JÖRG MÜLLER hat geantwortet.")]
public void AFullNameIsReplacedWithAndWithoutItsUmlauts(string text) => Assert.That(Anonymize(text, ["Jörg Müller"]), Is.EqualTo("[Person 1] hat geantwortet."));
[Test]
public void ANameWrittenOutIsAlsoReplacedWithItsUmlauts() => Assert.That(Anonymize("Herr Müller und Jörg", ["Joerg Mueller"]), Is.EqualTo("[Person 1] und [Person 1]"));
[TestCase("Prof. Dr. Anna Schmidt spricht.")]
[TestCase("Dr. Schmidt spricht.")]
[TestCase("Frau Schmidt spricht.")]
[TestCase("Prof.Dr. Anna Schmidt spricht.")]
[TestCase("Anna spricht.")]
public void TheTitlesGoWithTheName(string text) => Assert.That(Anonymize(text, ["Anna Schmidt"]), Is.EqualTo("[Person 1] spricht."));
[TestCase("Schmidts Studie", "[Person 1]s Studie")]
[TestCase("Anna Schmidts Studie", "[Person 1]s Studie")]
[TestCase("Anna's talk", "[Person 1]'s talk")]
[TestCase("Schmidt’s talk", "[Person 1]’s talk")]
[TestCase("„Schmidt“ sagte", "„[Person 1]“ sagte")]
public void TheGenitiveStaysAfterThePlaceholder(string text, string expected) => Assert.That(Anonymize(text, ["Anna Schmidt"]), Is.EqualTo(expected));
[Test]
public void TheGenitiveOfANameEndingWithSIsReplaced() => Assert.That(Anonymize("Schmitz' Buch", ["Lena Schmitz"]), Is.EqualTo("[Person 1]' Buch"));
[Test]
public void ALongerNameWinsOverTheShorterOneItContains()
{
var text = Anonymize("Anna Schmidt-Weber sprach mit Anna Schmidt. Schmidt-Weber widersprach.", ["Anna Schmidt", "Anna Schmidt-Weber"]);
Assert.That(text, Is.EqualTo("[Person 2] sprach mit [Person 1]. [Person 2] widersprach."));
}
[Test]
public void APartWhichSeveralPeopleShareGetsNoNumber()
{
var text = Anonymize("Schmidt sagte zu Anna Schmidt und Bernd Schmidt: Bernd!", ["Anna Schmidt", "Bernd Schmidt"]);
Assert.That(text, Is.EqualTo("[Person] sagte zu [Person 1] und [Person 2]: [Person 2]!"));
}
[Test]
public void APartOfANameOnItsOwnStaysInADoubleNameOfSomebodyElse() => Assert.That(Anonymize("Frau Müller-Schmidt kam.", ["Anna Schmidt"]), Is.EqualTo("Frau Müller-Schmidt kam."));
[Test]
public void APartOfANameOnItsOwnCountsOnlyWithACapitalLetter() => Assert.That(Anonymize("Der Topf ist klein, Klein kam.", ["Eva Klein"]), Is.EqualTo("Der Topf ist klein, [Person 1] kam."));
[TestCase("anna-schmidt")]
[TestCase("anna.schmidt")]
[TestCase("annaschmidt")]
[TestCase("schmidt_anna")]
[TestCase("AnnaSchmidt")]
public void TheFormsOfANameInAddressesAreReplaced(string form) => Assert.That(Anonymize($"Profil: @{form} und {form}-lab.de", ["Anna Schmidt"]), Is.EqualTo("Profil: @[Person 1] und [Person 1]-lab.de"));
[Test]
public void ANameWithTheLastNameFirstIsReplaced() => Assert.That(Anonymize("Schmidt, Anna (TUM)", ["Anna Schmidt"]), Is.EqualTo("[Person 1] (TUM)"));
[Test]
public void ANameListedWithTheLastNameFirstIsUnderstood() => Assert.That(Anonymize("Anna Schmidt kam.", ["Schmidt, Anna"]), Is.EqualTo("[Person 1] kam."));
[Test]
public void AParticleBelongsToTheLastName() => Assert.That(Anonymize("Frau von der Leyen und Leyen", ["Ursula von der Leyen"]), Is.EqualTo("[Person 1] und [Person 1]"));
[TestCase("Siehe https://example.org/team/anna?x=1.", "Siehe [Link].")]
[TestCase("(https://example.org/a)", "([Link])")]
[TestCase("Auf www.example.org steht es.", "Auf [Link] steht es.")]
[TestCase("Das [Programm](https://example.org/programm \"Titel\") lesen", "Das Programm [Link] lesen")]
[TestCase("<https://example.org/x>", "[Link]")]
[TestCase("Schreib an [Anna](mailto:anna@example.org)", "Schreib an Anna [Link]")]
public void LinksAreReplaced(string text, string expected) => Assert.That(Anonymize(text), Is.EqualTo(expected));
[TestCase("Mail: anna.schmidt+lab@tum.de.", "Mail: [E-mail address].")]
[TestCase("jörg@müller.example", "[E-mail address]")]
public void EMailAddressesAreReplaced(string text, string expected) => Assert.That(Anonymize(text), Is.EqualTo(expected));
[TestCase("Datei /Users/anna/Documents/talk.pdf liegt da.", "Datei [Path] liegt da.")]
[TestCase(@"Datei C:\Users\anna\talk.docx.", "Datei [Path].")]
[TestCase("Datei ~/Notes/talk.md", "Datei [Path]")]
[TestCase(@"Freigabe \\server\share\talk.pptx", "Freigabe [Path]")]
[TestCase("und/oder, 120 km/h, 1/2", "und/oder, 120 km/h, 1/2")]
public void PathsAreReplaced(string text, string expected) => Assert.That(Anonymize(text), Is.EqualTo(expected));
[TestCase("Tel. +49 89 123456-78.", "Tel. [Phone number].")]
[TestCase("Mobil 0176 12345678", "Mobil [Phone number]")]
[TestCase("Büro (089) 1234567", "Büro [Phone number]")]
[TestCase("Am 05.11.2026 von 08.00-09.30 Uhr", "Am 05.11.2026 von 08.00-09.30 Uhr")]
[TestCase("p = 0,05 bei 1234567 Fällen", "p = 0,05 bei 1234567 Fällen")]
public void PhoneNumbersAreReplaced(string text, string expected) => Assert.That(Anonymize(text), Is.EqualTo(expected));
[Test]
public void TermsAreReplacedInAnyCase()
{
var text = BflAnonymizer.Anonymize("Die TUM und die Technische Universität München, kurz tum.", [], ["TUM", "Technische Universität München"]).Text;
Assert.That(text, Is.EqualTo("Die [Term 1] und die [Term 2], kurz [Term 1]."));
}
[Test]
public void PlaceholdersStayAlsoWhenATermLooksLikeThem()
{
var text = BflAnonymizer.Anonymize("[Person 1] nannte [Link] und den Link.", [], ["Link", "Person"]).Text;
Assert.That(text, Is.EqualTo("[Person 1] nannte [Link] und den [Term 1]."));
}
[Test]
public void ASecondRunChangesNothing()
{
const string TEXT = """
Prof. Dr. Jörg Müller (joerg.mueller@tum.de, +49 89 123456-78) schrieb in /Users/joerg/Talk/notes.md:
Siehe https://example.org/joerg-mueller und [das Programm](https://example.org/p). Müllers These, Eva Klein und die TUM.
""";
var first = BflAnonymizer.Anonymize(TEXT, ["Jörg Müller", "Eva Klein"], ["TUM"]);
var second = BflAnonymizer.Anonymize(first.Text, ["Jörg Müller", "Eva Klein"], ["TUM"]);
using (Assert.EnterMultipleScope())
{
Assert.That(second.Text, Is.EqualTo(first.Text));
Assert.That(second.Replacements, Is.Empty);
}
}
[Test]
public void NoNameRemains()
{
const string TEXT = """
Prof. Dr. Jörg Müller-Lüdenscheidt, Joerg Mueller-Luedenscheidt und Jörg sprachen.
Müller-Lüdenscheidts Studie, Dr. Müller-Lüdenscheidt, joerg.mueller-luedenscheidt und @JoergMuellerLuedenscheidt.
Ursula von der Leyen, Frau von der Leyen, Leyens Rede, ursula-von-der-leyen.
""";
var text = Anonymize(TEXT, ["Jörg Müller-Lüdenscheidt", "Ursula von der Leyen"]);
using (Assert.EnterMultipleScope())
{
foreach (var part in new[] { "Jörg", "Joerg", "Müller", "Mueller", "Lüdenscheidt", "Luedenscheidt", "Ursula", "Leyen" })
Assert.That(text, Does.Not.Contain(part).IgnoreCase, part);
}
}
[Test]
public void TheReportCountsWhatWasReplaced()
{
var anonymization = BflAnonymizer.Anonymize("Anna Schmidt, Anna Schmidt und Dr. Schmidt: anna@example.org", ["Anna Schmidt"], []);
using (Assert.EnterMultipleScope())
{
Assert.That(anonymization.Replacements, Does.Contain(new BflReplacement("[Person 1]", "Anna Schmidt", 2)));
Assert.That(anonymization.Replacements, Does.Contain(new BflReplacement("[Person 1]", "Dr. Schmidt", 1)));
Assert.That(anonymization.Replacements, Does.Contain(new BflReplacement("[E-mail address]", "anna@example.org", 1)));
}
}
[Test]
public void AnEditedTextKeepsTheReportOfTheFirstRun()
{
var first = BflAnonymizer.Anonymize("Anna Schmidt", ["Anna Schmidt"], []);
var later = BflAnonymizer.Anonymize($"{first.Text} und Anna Schmidt", ["Anna Schmidt"], []);
var combined = first.CombinedWith(later);
using (Assert.EnterMultipleScope())
{
Assert.That(combined.Text, Is.EqualTo("[Person 1] und [Person 1]"));
Assert.That(combined.Replacements, Is.EqualTo(new[] { new BflReplacement("[Person 1]", "Anna Schmidt", 2) }));
}
}
[Test]
public void EmptyAndRepeatedEntriesGetNoNumber() => Assert.That(Anonymize("Eva Klein und Anna Schmidt", ["", " ", "Eva Klein", "eva klein", "Anna Schmidt"]), Is.EqualTo("[Person 1] und [Person 2]"));
private static string Anonymize(string text, IReadOnlyList<string>? people = null) => BflAnonymizer.Anonymize(text, people ?? [], []).Text;
}
@@ -0,0 +1,51 @@
using AIStudio.Assistants.BeyondFactsLab;
namespace AIStudio.Tests.BeyondFactsLab;
/// <summary>
/// Checks the mailto link with which a person sends a shared session to the research team of the BeyondFacts Lab.
/// </summary>
[TestFixture]
public sealed class BflShareMailTests
{
[Test]
public void TheLinkNamesTheResearchTeamAndItsSubject() => Assert.That(BflShareMail.CreateUri(), Does.StartWith($"mailto:{BflShareMail.RECIPIENT}?subject="));
[Test]
public void TheSubjectAndTheTextAreEncoded()
{
var uri = BflShareMail.CreateUri("team@example.org", "Müller & Co? 1+1", "Grüße\nund Dank");
Assert.That(uri, Is.EqualTo("mailto:team@example.org?subject=M%C3%BCller%20%26%20Co%3F%201%2B1&body=Gr%C3%BC%C3%9Fe%0D%0Aund%20Dank"));
}
[TestCase("Eins\nZwei")]
[TestCase("Eins\r\nZwei")]
[TestCase("Eins\rZwei")]
public void EveryLineBreakIsCrLf(string body) => Assert.That(BflShareMail.CreateUri("team@example.org", "Betreff", body), Does.EndWith("&body=Eins%0D%0AZwei"));
[Test]
public void ALongTextLosesItsEndAtAWord()
{
var body = string.Join(' ', Enumerable.Repeat("Wörter", 600));
var uri = BflShareMail.CreateUri("team@example.org", "Betreff", body);
var text = Uri.UnescapeDataString(uri[(uri.IndexOf("&body=", StringComparison.Ordinal) + "&body=".Length)..]);
using (Assert.EnterMultipleScope())
{
Assert.That(uri.Length, Is.LessThanOrEqualTo(BflShareMail.MAX_URI_LENGTH));
Assert.That(body, Does.StartWith(text));
Assert.That(text, Does.EndWith("Wörter"));
}
}
[Test]
public void TheTextOfTheLabFitsTheLimit() => Assert.That(BflShareMail.CreateUri(), Does.Contain("&body=").And.Length.LessThanOrEqualTo(BflShareMail.MAX_URI_LENGTH));
[Test]
public void TheTextOfTheLabHasCrLfOnly()
{
var uri = BflShareMail.CreateUri();
Assert.That(uri.Replace("%0D%0A", string.Empty), Does.Not.Contain("%0A").And.Not.Contain("%0D"));
}
}
@@ -0,0 +1,222 @@
using System.Globalization;
using AIStudio.Assistants.BeyondFactsLab;
namespace AIStudio.Tests.BeyondFactsLab;
/// <summary>
/// Checks what a session shared with the research team of the BeyondFacts Lab holds, before anonymizing it.
/// </summary>
/// <remarks>
/// The research team learns how the person worked with the lab. Whatever points to the person or to the event stays
/// out: the name of the preparation, the material, the research, the fields which pin down the event, and personal
/// disclosures unless the person includes them.
/// </remarks>
[TestFixture]
public sealed class BflShareModelBuilderTests
{
private static readonly DateTimeOffset NOW = new(2026, 11, 12, 9, 0, 0, TimeSpan.Zero);
[Test]
public void TheFieldsWhichPinDownTheEventStayOut()
{
var forms = Forms();
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.FORMAT, "Podium-4711");
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.EVENT_DATE, "DATUM-4711");
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.EVENT_LOCATION, "ORT-4711");
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.EVENT_LINK, "https://example.org/LINK-4711");
var session = Build(forms: forms);
using (Assert.EnterMultipleScope())
{
Assert.That(session, Does.Contain("Podium-4711"));
Assert.That(session, Does.Not.Contain("DATUM-4711"));
Assert.That(session, Does.Not.Contain("ORT-4711"));
Assert.That(session, Does.Not.Contain("LINK-4711"));
}
}
[Test]
public void TheLinkDateAndPlaceOfTheEventAreTheFieldsWhichPinItDown()
{
var identifying = BflFieldCatalog.FIELDS.Where(field => field.IdentifiesEvent).Select(field => field.Id);
string[] expected = [BflFieldCatalog.EVENT_DATE, BflFieldCatalog.EVENT_LOCATION, BflFieldCatalog.EVENT_LINK];
Assert.That(identifying, Is.EquivalentTo(expected));
}
[Test]
public void TheMaterialAppearsOnlyAsItsCount()
{
var materials = new List<BflMaterial>
{
new() { MaterialId = Guid.NewGuid(), Name = "Entwurf-Schmidt-4711.pdf", Kind = BflMaterialKind.DOCUMENT, AddedAtUtc = NOW },
new() { MaterialId = Guid.NewGuid(), Name = "Probe-4711.m4a", Kind = BflMaterialKind.RECORDING, AddedAtUtc = NOW },
new() { MaterialId = Guid.NewGuid(), Name = "Folie-4711.png", Kind = BflMaterialKind.DOCUMENT, AddedAtUtc = NOW },
};
var session = Build(materials: materials);
using (Assert.EnterMultipleScope())
{
Assert.That(session, Does.Not.Contain("4711"));
Assert.That(session, Does.Contain("2 documents, 1 recordings, 0 images"));
}
}
[Test]
public void PersonalDisclosuresStayOutUnlessThePersonIncludesThem()
{
var forms = Forms();
forms[BflStep.VALUES_AND_PERSPECTIVES].Set(BflFieldCatalog.PERSONAL_CONCERN, "FELD-PERSÖNLICH-4711");
forms[BflStep.VALUES_AND_PERSPECTIVES].Set(BflFieldCatalog.VALUES_AND_CONCERNS, "Gerechtigkeit");
var workStates = new Dictionary<BflStep, BflWorkState>();
Confirm(workStates, BflStep.VALUES_AND_PERSPECTIVES, new() { ["VALUES"] = "Offenheit", ["PERSONAL_INVOLVEMENT"] = "STAND-PERSÖNLICH-4711" });
var conversations = new Dictionary<BflStep, IReadOnlyList<BflConversationMessage>>
{
[BflStep.VALUES_AND_PERSPECTIVES] = [new(true, "GESPRÄCH-PERSÖNLICH-4711")],
};
var without = Build(forms: forms, workStates: workStates, conversations: conversations);
var with = Build(forms: forms, workStates: workStates, conversations: conversations, includePersonalDisclosures: true);
using (Assert.EnterMultipleScope())
{
Assert.That(without, Does.Not.Contain("PERSÖNLICH-4711"));
Assert.That(without, Does.Contain("Gerechtigkeit"));
Assert.That(without, Does.Contain("Offenheit"));
Assert.That(with, Does.Contain("FELD-PERSÖNLICH-4711"));
Assert.That(with, Does.Contain("STAND-PERSÖNLICH-4711"));
Assert.That(with, Does.Contain("GESPRÄCH-PERSÖNLICH-4711"));
}
}
[Test]
public void AConversationOfAStepWithoutPersonalDisclosuresAppearsWithItsSpeakers()
{
var conversations = new Dictionary<BflStep, IReadOnlyList<BflConversationMessage>>
{
[BflStep.KNOWLEDGE] = [new(true, "Frage-4711"), new(false, "Antwort-4711")],
};
var session = Build(conversations: conversations);
using (Assert.EnterMultipleScope())
{
Assert.That(session, Does.Contain("**Person:**\n\nFrage-4711\n"));
Assert.That(session, Does.Contain("**Lab:**\n\nAntwort-4711\n"));
}
}
[Test]
public void ChoicesAppearWithTheLabelsOfTheirOptions()
{
var forms = Forms();
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.EVENT_VISIBILITY, BflFieldCatalog.EVENT_NOT_PUBLIC);
var goals = BflFieldCatalog.FIELDS.Single(field => field.Id == BflFieldCatalog.GOAL_DIRECTIONS);
forms[BflStep.GOALS_AND_ROLE].SetChoices(goals, [BflFieldCatalog.GOAL_INFORM, BflFieldCatalog.GOAL_MODERATE]);
var session = Build(forms: forms);
var visibility = BflFieldCatalog.FIELDS.Single(field => field.Id == BflFieldCatalog.EVENT_VISIBILITY);
using (Assert.EnterMultipleScope())
{
Assert.That(session, Does.Contain(visibility.Options.Single(option => option.Id == BflFieldCatalog.EVENT_NOT_PUBLIC).Label));
Assert.That(session, Does.Contain($"{goals.Options[0].Label}, {goals.Options[1].Label}"));
Assert.That(session, Does.Not.Contain(BflFieldCatalog.EVENT_NOT_PUBLIC));
Assert.That(session, Does.Not.Contain(BflFieldCatalog.GOAL_INFORM));
}
}
[Test]
public void DraftsAndTheReflectionFolderNeverAppear()
{
var workStates = new Dictionary<BflStep, BflWorkState>
{
[BflStep.KNOWLEDGE] = new() { Draft = new(StringComparer.Ordinal) { ["ESTABLISHED"] = "ENTWURF-4711" } },
};
var results = new Dictionary<BflResultKind, BflResult>
{
[BflResultKind.POSITION_PAPER] = new() { Draft = new() { Text = "MATERIAL-ENTWURF-4711" } },
[BflResultKind.REFLECTION_FOLDER] = new() { Confirmed = new() { Text = "MAPPE-4711" } },
};
Assert.That(Build(workStates: workStates, results: results), Does.Not.Contain("4711"));
}
[Test]
public void TheConfirmedMaterialsOfTheLabAppearInTheLastStep()
{
var results = new Dictionary<BflResultKind, BflResult>
{
[BflResultKind.POSITION_PAPER] = new() { Confirmed = new() { Text = "Position-4711" } },
};
var session = Build(results: results);
using (Assert.EnterMultipleScope())
{
Assert.That(session, Does.Contain($"### {BflResultKind.POSITION_PAPER.Title}\n\nPosition-4711\n"));
Assert.That(session.IndexOf("Position-4711", StringComparison.Ordinal), Is.GreaterThan(session.IndexOf($"## {BflStep.RESULTS.Title}\n", StringComparison.Ordinal)));
}
}
[Test]
public void TheTopicsOfTheResearchAppearByTheirTitles()
{
var session = Build(researchTopics: [BflResearchTopic.DEBATE, BflResearchTopic.EVENT]);
Assert.That(session, Does.Contain($"{BflResearchTopic.EVENT.Title}, {BflResearchTopic.DEBATE.Title}"));
}
[Test]
public void EveryStepAppearsInItsOrder()
{
var session = Build();
var positions = BflStep.All.Select(step => session.IndexOf($"## {step.Title}\n", StringComparison.Ordinal)).ToList();
using (Assert.EnterMultipleScope())
{
Assert.That(positions, Has.All.GreaterThanOrEqualTo(0));
Assert.That(positions, Is.Ordered);
}
}
[Test]
public void EveryLineEndsWithLf()
{
var forms = Forms();
forms[BflStep.KNOWLEDGE].Set(BflFieldCatalog.CORE_THESES, "Erste\r\nZweite");
var conversations = new Dictionary<BflStep, IReadOnlyList<BflConversationMessage>>
{
[BflStep.KNOWLEDGE] = [new(false, "Eins\r\nZwei")],
};
Assert.That(Build(forms: forms, conversations: conversations), Does.Not.Contain("\r"));
}
private static string Build(Dictionary<BflStep, BflStepFormData>? forms = null, IReadOnlyDictionary<BflStep, BflWorkState>? workStates = null, IReadOnlyDictionary<BflResultKind, BflResult>? results = null, IReadOnlyDictionary<BflStep, IReadOnlyList<BflConversationMessage>>? conversations = null, IReadOnlyList<BflMaterial>? materials = null, IReadOnlyCollection<BflResearchTopic>? researchTopics = null, bool includePersonalDisclosures = false) => BflShareModelBuilder.Build(new BflShareInput(
forms ?? Forms(),
workStates ?? new Dictionary<BflStep, BflWorkState>(),
results ?? new Dictionary<BflResultKind, BflResult>(),
conversations ?? new Dictionary<BflStep, IReadOnlyList<BflConversationMessage>>(),
materials ?? [],
researchTopics ?? [],
includePersonalDisclosures,
"26.11.2",
NOW,
CultureInfo.InvariantCulture));
private static Dictionary<BflStep, BflStepFormData> Forms() => BflStep.All.ToDictionary(step => step, _ => new BflStepFormData());
private static void Confirm(Dictionary<BflStep, BflWorkState> workStates, BflStep step, Dictionary<string, string> sections)
{
if (!workStates.TryGetValue(step, out var workState))
{
workState = new BflWorkState();
workStates[step] = workState;
}
workState.Confirm(sections, BflStaleness.UpstreamRevisionsOf(step, workStates), BflStaleness.FingerprintOf(new BflStepFormData()), NOW);
}
}
@@ -0,0 +1,71 @@
using AIStudio.Assistants.BeyondFactsLab;
namespace AIStudio.Tests.BeyondFactsLab;
/// <summary>
/// Checks which names the BeyondFacts Lab suggests to remove before the person shares a session.
/// </summary>
[TestFixture]
public sealed class BflShareNamesTests
{
private static readonly DateTimeOffset NOW = new(2026, 11, 12, 9, 0, 0, TimeSpan.Zero);
[Test]
public void TheNamesInTheFieldOfTheOtherParticipantsAreSuggested()
{
var forms = Forms();
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.OTHER_PARTICIPANTS, "Prof. Dr. Anna Schmidt (TUM)\nModeration: Bernd Weber; Carla von der Heide und Dieter Graf, Bundestag\n- Die Moderatorin Hanna Berg\nVertreter*innen aus der Politik");
var names = BflShareNames.Suggest(forms, new Dictionary<BflStep, BflWorkState>(), null);
Assert.That(names, Is.EqualTo(new[] { "Anna Schmidt", "Bernd Weber", "Carla von der Heide", "Dieter Graf", "Bundestag", "Hanna Berg" }));
}
[Test]
public void TheHeadingsOfTheResearchOnTheParticipantsAreSuggested()
{
var research = new BflResearch
{
Text = "## Teilnehmende\n\n### Dr. Eva Klein\n\n- Rolle: Podium\n\n### Frank Ott (Moderation)\n\n## Offene Punkte\n\n- Wer noch?",
CreatedAtUtc = NOW,
};
var names = BflShareNames.Suggest(Forms(), new Dictionary<BflStep, BflWorkState>(), research);
Assert.That(names, Is.EqualTo(new[] { "Eva Klein", "Frank Ott" }));
}
[Test]
public void TheNamesInBoldInTheSectionOnTheParticipantsAreSuggested()
{
var workStates = new Dictionary<BflStep, BflWorkState> { [BflStep.STRATEGY] = new() };
workStates[BflStep.STRATEGY].Confirm(new Dictionary<string, string> { ["PARTICIPANTS"] = "- **Gregor Sand**, Moderation, hält das für **wichtig**\n- [**Ida Lux**](https://example.org)" }, new Dictionary<BflStep, int>(), string.Empty, NOW);
var names = BflShareNames.Suggest(Forms(), workStates, null);
Assert.That(names, Is.EqualTo(new[] { "Gregor Sand", "Ida Lux" }));
}
[Test]
public void ANameFoundTwiceOrAsAPartOfALongerOneIsSuggestedOnce()
{
var forms = Forms();
forms[BflStep.FRAMEWORK].Set(BflFieldCatalog.OTHER_PARTICIPANTS, "Weber\nBernd Weber\nbernd weber");
var names = BflShareNames.Suggest(forms, new Dictionary<BflStep, BflWorkState>(), null);
Assert.That(names, Is.EqualTo(new[] { "Bernd Weber" }));
}
[Test]
public void ALongRunOfCapitalsGivesItsLastTwoWords() => Assert.That(BflShareNames.NamesIn("Neue Forschung Zeigt Lena Graf"), Is.EqualTo(new[] { "Lena Graf" }));
[Test]
public void ADraftOfAWorkStateSuggestsNothing()
{
var workStates = new Dictionary<BflStep, BflWorkState>
{
[BflStep.STRATEGY] = new() { Draft = new(StringComparer.Ordinal) { ["PARTICIPANTS"] = "**Gregor Sand**" } },
};
Assert.That(BflShareNames.Suggest(Forms(), workStates, null), Is.Empty);
}
private static Dictionary<BflStep, BflStepFormData> Forms() => BflStep.All.ToDictionary(step => step, _ => new BflStepFormData());
}