mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-13 11:27:21 +00:00
Added sharing a BeyondFacts Lab session with the research team (#1196)
This commit is contained in:
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());
|
||||
}
|
||||
Reference in new issue
Block a user