mirror of
https://github.com/MindWorkAI/AI-Studio.git
synced 2026-11-10 21:47:21 +00:00
Added confirmed work states to the BeyondFacts Lab (#1182)
This commit is contained in:
1 parent
6d0052ecdf
commit
a9579dd282
39 files changed
+3160
-16
No files matched your search
@@ -0,0 +1,125 @@
|
||||
using AIStudio.Assistants.BeyondFactsLab;
|
||||
|
||||
using Microsoft.Extensions.Logging.Abstractions;
|
||||
|
||||
namespace AIStudio.Tests.BeyondFactsLab;
|
||||
|
||||
/// <summary>
|
||||
/// Checks how the BeyondFacts Lab stores the work states of the steps.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class BeyondFactsLabStoreWorkStateTests
|
||||
{
|
||||
private string dataDirectory = string.Empty;
|
||||
|
||||
[SetUp]
|
||||
public void CreateDataDirectory()
|
||||
{
|
||||
this.dataDirectory = Path.Combine(Path.GetTempPath(), $"ai-studio-beyondfacts-lab-{Guid.NewGuid():N}");
|
||||
Directory.CreateDirectory(this.dataDirectory);
|
||||
}
|
||||
|
||||
[TearDown]
|
||||
public void DeleteDataDirectory()
|
||||
{
|
||||
if (Directory.Exists(this.dataDirectory))
|
||||
Directory.Delete(this.dataDirectory, true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AStepWithoutAWorkStateGetsAnEmptyOne()
|
||||
{
|
||||
var store = this.CreateStore();
|
||||
var created = await store.CreateAsync("Panel");
|
||||
|
||||
var workState = await store.LoadWorkStateAsync(created.ProjectId, BflStep.KNOWLEDGE);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(workState.Draft, Is.Null);
|
||||
Assert.That(workState.Confirmed, Is.Null);
|
||||
Assert.That(workState.FieldSuggestions, Is.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AWorkStateIsStoredWithItsConfirmationAndSuggestions()
|
||||
{
|
||||
var store = this.CreateStore();
|
||||
var created = await store.CreateAsync("Panel");
|
||||
var confirmedAtUtc = new DateTimeOffset(2026, 11, 10, 9, 0, 0, TimeSpan.Zero);
|
||||
var workState = new BflWorkState { FieldSuggestions = { [BflFieldCatalog.CORE_THESES] = "These" } };
|
||||
workState.Confirm(new Dictionary<string, string> { ["SUMMARY"] = "Stand" }, new Dictionary<BflStep, int> { [BflStep.FRAMEWORK] = 3 }, "fingerprint", confirmedAtUtc);
|
||||
workState.Draft = new(StringComparer.Ordinal) { ["SUMMARY"] = "Entwurf" };
|
||||
|
||||
var changed = await store.SaveWorkStateAsync(created.ProjectId, BflStep.KNOWLEDGE, workState);
|
||||
var loaded = await this.CreateStore().LoadWorkStateAsync(created.ProjectId, BflStep.KNOWLEDGE);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(changed.ModifiedAtUtc, Is.GreaterThanOrEqualTo(created.ModifiedAtUtc));
|
||||
Assert.That(loaded.Draft, Is.EqualTo(workState.Draft));
|
||||
Assert.That(loaded.LastRevision, Is.EqualTo(1));
|
||||
Assert.That(loaded.Confirmed!.Revision, Is.EqualTo(1));
|
||||
Assert.That(loaded.Confirmed.ConfirmedAtUtc, Is.EqualTo(confirmedAtUtc));
|
||||
Assert.That(loaded.Confirmed.Sections, Is.EqualTo(workState.Confirmed!.Sections));
|
||||
Assert.That(loaded.Confirmed.UpstreamRevisions, Is.EqualTo(workState.Confirmed.UpstreamRevisions));
|
||||
Assert.That(loaded.Confirmed.DetailsFingerprint, Is.EqualTo("fingerprint"));
|
||||
Assert.That(loaded.FieldSuggestions, Is.EqualTo(workState.FieldSuggestions));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task ADamagedWorkStateIsNeitherOpenedNorOverwritten()
|
||||
{
|
||||
const string DAMAGED = "{ \"draft\": ";
|
||||
var store = this.CreateStore();
|
||||
var created = await store.CreateAsync("Panel");
|
||||
var path = await this.WriteWorkStateFileAsync(created.ProjectId, "02-knowledge", DAMAGED);
|
||||
|
||||
var workStates = await store.LoadWorkStatesAsync(created.ProjectId);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(workStates.ContainsKey(BflStep.KNOWLEDGE), Is.False);
|
||||
Assert.That(workStates.ContainsKey(BflStep.FRAMEWORK), Is.True);
|
||||
await Assert.ThrowsAsync<InvalidDataException>(async () => await store.LoadWorkStateAsync(created.ProjectId, BflStep.KNOWLEDGE));
|
||||
await Assert.ThrowsAsync<InvalidDataException>(async () => await store.SaveWorkStateAsync(created.ProjectId, BflStep.KNOWLEDGE, new BflWorkState()));
|
||||
Assert.That(await File.ReadAllTextAsync(path), Is.EqualTo(DAMAGED));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AWorkStateOfANewerVersionIsNotOverwritten()
|
||||
{
|
||||
const string NEWER = """{ "schemaVersion": 2, "fieldSuggestions": {} }""";
|
||||
var store = this.CreateStore();
|
||||
var created = await store.CreateAsync("Panel");
|
||||
var path = await this.WriteWorkStateFileAsync(created.ProjectId, "05-strategy", NEWER);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
await Assert.ThrowsAsync<InvalidDataException>(async () => await store.SaveWorkStateAsync(created.ProjectId, BflStep.STRATEGY, new BflWorkState()));
|
||||
Assert.That(await File.ReadAllTextAsync(path), Is.EqualTo(NEWER));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task AWorkStateOfAMissingPreparationIsNotWritten()
|
||||
{
|
||||
var store = this.CreateStore();
|
||||
|
||||
await Assert.ThrowsAsync<FileNotFoundException>(async () => await store.SaveWorkStateAsync(Guid.NewGuid(), BflStep.FRAMEWORK, new BflWorkState()));
|
||||
}
|
||||
|
||||
private async Task<string> WriteWorkStateFileAsync(Guid projectId, string stepFolder, string content)
|
||||
{
|
||||
var directory = Path.Combine(this.dataDirectory, BeyondFactsLabStore.ROOT_DIRECTORY_NAME, projectId.ToString("D"), "steps", stepFolder);
|
||||
Directory.CreateDirectory(directory);
|
||||
var path = Path.Combine(directory, "work-state.json");
|
||||
await File.WriteAllTextAsync(path, content);
|
||||
return path;
|
||||
}
|
||||
|
||||
private BeyondFactsLabStore CreateStore() => new(NullLogger<BeyondFactsLabStore>.Instance, new BflStorageOptions { DataDirectory = this.dataDirectory });
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
using AIStudio.Assistants.BeyondFactsLab;
|
||||
|
||||
namespace AIStudio.Tests.BeyondFactsLab;
|
||||
|
||||
/// <summary>
|
||||
/// Checks how the BeyondFacts Lab reads a work state which a model wrote as Markdown.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// The keys of the sections are stored in every work state, so they are pinned here as well.
|
||||
/// </remarks>
|
||||
[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/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<string, string>
|
||||
{
|
||||
[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 OnlyEmptyTextFieldsWhichAreNotPersonalGetSuggestions()
|
||||
{
|
||||
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(values, Is.EqualTo(new[] { BflFieldCatalog.VALUES_AND_CONCERNS, BflFieldCatalog.OTHER_PERSPECTIVES }));
|
||||
Assert.That(test, Is.EqualTo(new[] { BflFieldCatalog.TEST_NOTES }));
|
||||
}
|
||||
}
|
||||
|
||||
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}";
|
||||
}
|
||||
@@ -67,11 +67,13 @@ public sealed class BflPromptLibraryTests
|
||||
{
|
||||
var core = string.Join("\n", BflPromptLibrary.PlaceholdersOf(BflPromptLibrary.CORE_PATH).Select(name => $"{{{{{name}}}}}"));
|
||||
var onboarding = string.Join("\n", BflPromptLibrary.PlaceholdersOf(BflPromptLibrary.ONBOARDING_PATH).Select(name => $"{{{{{name}}}}}"));
|
||||
var workState = string.Join("\n", BflPromptLibrary.PlaceholdersOf(BflPromptLibrary.WORK_STATE_PATH).Select(name => $"{{{{{name}}}}}"));
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(BflPromptComposer.Compose(new(core, "Leitfaden", "Schritt", "Anrede"), BflStep.FRAMEWORK, new Dictionary<BflStep, BflStepFormData>(), [], "Deutsch"), Does.Not.Contain("{{"));
|
||||
Assert.That(BflPromptComposer.ComposeOnboarding(new(onboarding, "Anrede"), "Deutsch"), Does.Not.Contain("{{"));
|
||||
Assert.That(BflPromptComposer.ComposeWorkStateRequest(workState, BflStep.FRAMEWORK, new BflStepFormData()), Does.Not.Contain("{{"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,241 @@
|
||||
using AIStudio.Assistants.BeyondFactsLab;
|
||||
|
||||
namespace AIStudio.Tests.BeyondFactsLab;
|
||||
|
||||
/// <summary>
|
||||
/// Checks when a confirmed work state of the BeyondFacts Lab counts as outdated.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A confirmed work state is outdated as soon as the work state of an earlier step is confirmed for the first time,
|
||||
/// confirmed anew, or withdrawn. A changed form of its own step gives a weaker notice.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class BflStalenessTests
|
||||
{
|
||||
private static readonly DateTimeOffset NOW = new(2026, 11, 10, 9, 0, 0, TimeSpan.Zero);
|
||||
|
||||
private static readonly Dictionary<string, string> SECTIONS = new(StringComparer.Ordinal) { ["SUMMARY"] = "Text" };
|
||||
|
||||
[Test]
|
||||
public void AStepWithoutAConfirmationIsNotConfirmed()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState> { [BflStep.KNOWLEDGE] = new() { Draft = new(SECTIONS) } };
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.KNOWLEDGE, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.NOT_CONFIRMED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AConfirmationStaysValidWhileNothingChanges()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
Confirm(workStates, BflStep.KNOWLEDGE);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(BflStaleness.Of(BflStep.FRAMEWORK, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.CONFIRMED));
|
||||
Assert.That(BflStaleness.Of(BflStep.KNOWLEDGE, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.CONFIRMED));
|
||||
}
|
||||
}
|
||||
|
||||
[TestCase(Change.CONFIRMED_ANEW, TestName = "AnEarlierStepConfirmedAnewOutdatesTheLaterOnes")]
|
||||
[TestCase(Change.WITHDRAWN, TestName = "AnEarlierStepWithdrawnOutdatesTheLaterOnes")]
|
||||
[TestCase(Change.WITHDRAWN_AND_CONFIRMED_AGAIN, TestName = "AnEarlierStepWithdrawnAndConfirmedAgainOutdatesTheLaterOnes")]
|
||||
public void AChangeOfAnEarlierStepOutdatesTheLaterOnes(Change change)
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
Confirm(workStates, BflStep.GOALS_AND_ROLE);
|
||||
|
||||
switch (change)
|
||||
{
|
||||
case Change.CONFIRMED_ANEW:
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
break;
|
||||
|
||||
case Change.WITHDRAWN:
|
||||
workStates[BflStep.FRAMEWORK].Withdraw();
|
||||
break;
|
||||
|
||||
case Change.WITHDRAWN_AND_CONFIRMED_AGAIN:
|
||||
workStates[BflStep.FRAMEWORK].Withdraw();
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
break;
|
||||
}
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.GOALS_AND_ROLE, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.OUTDATED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnEarlierStepConfirmedForTheFirstTimeOutdatesTheLaterOnes()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.STRATEGY);
|
||||
|
||||
Confirm(workStates, BflStep.KNOWLEDGE);
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.STRATEGY, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.OUTDATED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ALaterStepNeverOutdatesAnEarlierOne()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.KNOWLEDGE);
|
||||
|
||||
Confirm(workStates, BflStep.STRATEGY);
|
||||
Confirm(workStates, BflStep.STRATEGY);
|
||||
workStates[BflStep.STRATEGY].Withdraw();
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.KNOWLEDGE, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.CONFIRMED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ConfirmingAnOutdatedStepAgainMakesItValid()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.GOALS_AND_ROLE);
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
|
||||
Confirm(workStates, BflStep.GOALS_AND_ROLE);
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.GOALS_AND_ROLE, workStates, Forms()), Is.EqualTo(BflWorkStateStatus.CONFIRMED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ChangedDetailsOfTheStepGiveTheWeakerNotice()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
var form = new BflStepFormData();
|
||||
form.Set(BflFieldCatalog.CONTRIBUTION_SUMMARY, "Erste Fassung");
|
||||
Confirm(workStates, BflStep.KNOWLEDGE, form);
|
||||
|
||||
form.Set(BflFieldCatalog.CONTRIBUTION_SUMMARY, "Zweite Fassung");
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.KNOWLEDGE, workStates, Forms((BflStep.KNOWLEDGE, form))), Is.EqualTo(BflWorkStateStatus.DETAILS_CHANGED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnOutdatedStepWeighsMoreThanChangedDetails()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
var form = new BflStepFormData();
|
||||
Confirm(workStates, BflStep.KNOWLEDGE, form);
|
||||
|
||||
form.Set(BflFieldCatalog.CORE_THESES, "Neu");
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.KNOWLEDGE, workStates, Forms((BflStep.KNOWLEDGE, form))), Is.EqualTo(BflWorkStateStatus.OUTDATED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AFormThisVersionCannotOpenChangesNothing()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
var form = new BflStepFormData();
|
||||
form.Set(BflFieldCatalog.CORE_THESES, "These");
|
||||
Confirm(workStates, BflStep.KNOWLEDGE, form);
|
||||
|
||||
Assert.That(BflStaleness.Of(BflStep.KNOWLEDGE, workStates, new Dictionary<BflStep, BflStepFormData>()), Is.EqualTo(BflWorkStateStatus.CONFIRMED));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheFingerprintIgnoresLineEndingsAndSpacesAroundValues()
|
||||
{
|
||||
var unix = new BflStepFormData();
|
||||
unix.Set(BflFieldCatalog.CORE_THESES, "Erste These\nZweite These");
|
||||
var windows = new BflStepFormData();
|
||||
windows.Set(BflFieldCatalog.CORE_THESES, " Erste These\r\nZweite These \n");
|
||||
var changed = new BflStepFormData();
|
||||
changed.Set(BflFieldCatalog.CORE_THESES, "Erste These\nDritte These");
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(BflStaleness.FingerprintOf(windows), Is.EqualTo(BflStaleness.FingerprintOf(unix)));
|
||||
Assert.That(BflStaleness.FingerprintOf(changed), Is.Not.EqualTo(BflStaleness.FingerprintOf(unix)));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheFingerprintTellsWhichFieldHoldsAValue()
|
||||
{
|
||||
var first = new BflStepFormData();
|
||||
first.Set(BflFieldCatalog.CORE_THESES, "Text");
|
||||
var second = new BflStepFormData();
|
||||
second.Set(BflFieldCatalog.UNCERTAINTIES, "Text");
|
||||
|
||||
Assert.That(BflStaleness.FingerprintOf(first), Is.Not.EqualTo(BflStaleness.FingerprintOf(second)));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AConfirmationRemembersOnlyEarlierSteps()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.FRAMEWORK);
|
||||
Confirm(workStates, BflStep.STRATEGY);
|
||||
Confirm(workStates, BflStep.KNOWLEDGE);
|
||||
|
||||
var revisions = BflStaleness.UpstreamRevisionsOf(BflStep.GOALS_AND_ROLE, workStates);
|
||||
|
||||
Assert.That(revisions.Keys, Is.EquivalentTo(new[] { BflStep.FRAMEWORK, BflStep.KNOWLEDGE }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ARevisionKeepsCountingAfterAWithdrawal()
|
||||
{
|
||||
var workState = new BflWorkState();
|
||||
workState.Confirm(SECTIONS, new Dictionary<BflStep, int>(), string.Empty, NOW);
|
||||
workState.Withdraw();
|
||||
workState.Confirm(SECTIONS, new Dictionary<BflStep, int>(), string.Empty, NOW);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(workState.Confirmed!.Revision, Is.EqualTo(2));
|
||||
Assert.That(workState.LastRevision, Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AConfirmationDropsTheDraftAndEmptySections()
|
||||
{
|
||||
var workState = new BflWorkState { Draft = new(SECTIONS) };
|
||||
|
||||
workState.Confirm(new Dictionary<string, string> { ["SUMMARY"] = "Text", ["OPEN_POINTS"] = " " }, new Dictionary<BflStep, int>(), string.Empty, NOW);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(workState.Draft, Is.Null);
|
||||
Assert.That(workState.Confirmed!.Sections.Keys, Is.EqualTo(new[] { "SUMMARY" }));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// What happens to an earlier step.
|
||||
/// </summary>
|
||||
public enum Change
|
||||
{
|
||||
CONFIRMED_ANEW,
|
||||
WITHDRAWN,
|
||||
WITHDRAWN_AND_CONFIRMED_AGAIN,
|
||||
}
|
||||
|
||||
private static void Confirm(Dictionary<BflStep, BflWorkState> workStates, BflStep step, BflStepFormData? form = null)
|
||||
{
|
||||
if (!workStates.TryGetValue(step, out var workState))
|
||||
{
|
||||
workState = new BflWorkState();
|
||||
workStates[step] = workState;
|
||||
}
|
||||
|
||||
workState.Confirm(SECTIONS, BflStaleness.UpstreamRevisionsOf(step, workStates), BflStaleness.FingerprintOf(form ?? new BflStepFormData()), NOW);
|
||||
}
|
||||
|
||||
private static Dictionary<BflStep, BflStepFormData> Forms(params (BflStep Step, BflStepFormData Form)[] forms)
|
||||
{
|
||||
var result = BflStep.All.ToDictionary(step => step, _ => new BflStepFormData());
|
||||
foreach (var (step, form) in forms)
|
||||
result[step] = form;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
using AIStudio.Assistants.BeyondFactsLab;
|
||||
using AIStudio.Settings.DataModel;
|
||||
|
||||
namespace AIStudio.Tests.BeyondFactsLab;
|
||||
|
||||
/// <summary>
|
||||
/// Checks how the confirmed work states of the BeyondFacts Lab reach the prompts of the steps.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Only what the person confirmed reaches a prompt: a draft never does, since the person has not checked it yet.
|
||||
/// </remarks>
|
||||
[TestFixture]
|
||||
public sealed class BflWorkStatePromptTests
|
||||
{
|
||||
private static readonly BflPromptParts PARTS = new("{{WORK_STATES}}", "Leitfaden", "Schritt", "Anrede");
|
||||
|
||||
private static readonly DateTimeOffset NOW = new(2026, 11, 10, 9, 0, 0, TimeSpan.Zero);
|
||||
|
||||
[Test]
|
||||
public void ADraftNeverReachesThePrompt()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>
|
||||
{
|
||||
[BflStep.KNOWLEDGE] = new() { Draft = new(StringComparer.Ordinal) { ["ESTABLISHED"] = "ENTWURF-4711" } },
|
||||
};
|
||||
|
||||
var prompt = Compose(BflStep.STRATEGY, workStates);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(prompt, Does.Not.Contain("ENTWURF-4711"));
|
||||
Assert.That(prompt, Is.EqualTo("Die Person hat noch keinen Arbeitsstand bestätigt."));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AConfirmedWorkStateReachesThePromptsOfOtherStepsWithItsSections()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.KNOWLEDGE, new() { ["ESTABLISHED"] = "BESTÄTIGT-4711", ["UNCERTAINTIES"] = " " });
|
||||
|
||||
var prompt = Compose(BflStep.STRATEGY, workStates);
|
||||
var established = BflWorkStateSections.ForStep(BflStep.KNOWLEDGE).Single(section => section.Key == "ESTABLISHED");
|
||||
var uncertainties = BflWorkStateSections.ForStep(BflStep.KNOWLEDGE).Single(section => section.Key == "UNCERTAINTIES");
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(prompt, Does.Contain($"## {BflStep.KNOWLEDGE.Title}\n### {established.Title}\nBESTÄTIGT-4711"));
|
||||
Assert.That(prompt, Does.Not.Contain(uncertainties.Title), "An empty section is left out.");
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheWorkStateOfTheOwnStepIsMarked()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.GOALS_AND_ROLE, new() { ["GOAL"] = "Informieren" });
|
||||
|
||||
var prompt = Compose(BflStep.GOALS_AND_ROLE, workStates);
|
||||
|
||||
Assert.That(prompt, Does.StartWith($"## {BflStep.GOALS_AND_ROLE.Title} (dieser Schritt)"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void APersonalSectionReachesOnlyTheChatOfItsOwnStep()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.VALUES_AND_PERSPECTIVES, new() { ["VALUES"] = "Gerechtigkeit", ["PERSONAL_INVOLVEMENT"] = "PERSÖNLICH-4711" });
|
||||
|
||||
var own = Compose(BflStep.VALUES_AND_PERSPECTIVES, workStates);
|
||||
var other = Compose(BflStep.STRATEGY, workStates);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(own, Does.Contain("PERSÖNLICH-4711"));
|
||||
Assert.That(own, Does.Contain("(persönliche Angabe)"));
|
||||
Assert.That(other, Does.Not.Contain("PERSÖNLICH-4711"));
|
||||
Assert.That(other, Does.Contain("Gerechtigkeit"));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AWorkStateWithOnlyAPersonalSectionIsLeftOutOfOtherSteps()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.VALUES_AND_PERSPECTIVES, new() { ["PERSONAL_INVOLVEMENT"] = "PERSÖNLICH-4711" });
|
||||
|
||||
Assert.That(Compose(BflStep.STRATEGY, workStates), Is.EqualTo("Die Person hat noch keinen Arbeitsstand bestätigt."));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void AnOutdatedWorkStateIsMarked()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.GOALS_AND_ROLE, new() { ["GOAL"] = "Informieren" });
|
||||
Confirm(workStates, BflStep.FRAMEWORK, new() { ["FORMAT"] = "Podium" });
|
||||
|
||||
var prompt = Compose(BflStep.STRATEGY, workStates);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(prompt, Does.Contain($"## {BflStep.GOALS_AND_ROLE.Title} (veraltet)"));
|
||||
Assert.That(prompt, Does.Contain($"## {BflStep.FRAMEWORK.Title}\n"));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ASectionThisVersionDoesNotKnowIsLeftOut()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.GOALS_AND_ROLE, new() { ["GOAL"] = "Informieren", ["FUTURE_SECTION"] = "ZUKUNFT-4711" });
|
||||
|
||||
Assert.That(Compose(BflStep.STRATEGY, workStates), Does.Not.Contain("ZUKUNFT-4711"));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task TheBuiltInPromptOfEveryStepCarriesTheConfirmedWorkStates()
|
||||
{
|
||||
var workStates = new Dictionary<BflStep, BflWorkState>();
|
||||
Confirm(workStates, BflStep.FRAMEWORK, new() { ["FORMAT"] = "BESTÄTIGT-4711" });
|
||||
|
||||
foreach (var step in BflStep.All)
|
||||
{
|
||||
var parts = await BflPromptLibrary.LoadPartsAsync(step, AddressForm.FORMAL);
|
||||
var prompt = BflPromptComposer.Compose(parts, step, new Dictionary<BflStep, BflStepFormData>(), [], "Deutsch", workStates);
|
||||
|
||||
Assert.That(prompt, Does.Contain("BESTÄTIGT-4711"), step.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private static string Compose(BflStep step, IReadOnlyDictionary<BflStep, BflWorkState> workStates) => BflPromptComposer.Compose(PARTS, step, new Dictionary<BflStep, BflStepFormData>(), [], "Deutsch", workStates);
|
||||
|
||||
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,72 @@
|
||||
using AIStudio.Assistants.BeyondFactsLab;
|
||||
|
||||
namespace AIStudio.Tests.BeyondFactsLab;
|
||||
|
||||
/// <summary>
|
||||
/// Checks the request which lets the model write the work state of a step in the BeyondFacts Lab.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public sealed class BflWorkStateRequestTests
|
||||
{
|
||||
[Test]
|
||||
public async Task TheRequestOfEveryStepNamesEachOfItsSectionsWithItsHeading()
|
||||
{
|
||||
var template = await BflPromptLibrary.ReadAsync(BflPromptLibrary.WORK_STATE_PATH);
|
||||
foreach (var step in BflStep.All)
|
||||
{
|
||||
var request = BflPromptComposer.ComposeWorkStateRequest(template, step, new BflStepFormData());
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(request, Does.Not.Contain("{{"), step.ToString());
|
||||
foreach (var section in BflWorkStateSections.ForStep(step))
|
||||
Assert.That(request, Does.Contain($"## [{section.Key}] {section.Title}"), $"{step}: {section.Key}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheRequestOffersSuggestionsOnlyForEmptyTextFields()
|
||||
{
|
||||
var form = new BflStepFormData();
|
||||
form.Set(BflFieldCatalog.FORMAT, "Podium");
|
||||
|
||||
var request = BflPromptComposer.ComposeWorkStateRequest("{{SECTIONS}}\n{{FIELDS}}", BflStep.FRAMEWORK, form);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(request, Does.Contain($"[{BflMarkdownSections.FIELD_PREFIX}{BflFieldCatalog.AUDIENCE}]"));
|
||||
Assert.That(request, Does.Contain($"[{BflMarkdownSections.FIELD_PREFIX}{BflFieldCatalog.EVENT_LINK}]"));
|
||||
Assert.That(request, Does.Not.Contain($"[{BflMarkdownSections.FIELD_PREFIX}{BflFieldCatalog.FORMAT}]"));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ARequestWithoutEmptyFieldsAsksForNoSuggestions()
|
||||
{
|
||||
var form = new BflStepFormData();
|
||||
form.Set(BflFieldCatalog.COMMUNICATION_WORRIES, "Sorge");
|
||||
|
||||
var request = BflPromptComposer.ComposeWorkStateRequest("{{FIELDS}}", BflStep.STRATEGY, form);
|
||||
|
||||
Assert.That(request, Does.Not.Contain(BflMarkdownSections.FIELD_PREFIX));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheRequestToCorrectAWorkStateNamesEveryProblem()
|
||||
{
|
||||
var request = BflPromptComposer.ComposeWorkStateRepairRequest(["Der Abschnitt [GOAL] fehlt.", "Den Abschnitt [X] gibt es in diesem Schritt nicht."]);
|
||||
|
||||
using (Assert.EnterMultipleScope())
|
||||
{
|
||||
Assert.That(request, Does.Contain("- Der Abschnitt [GOAL] fehlt."));
|
||||
Assert.That(request, Does.Contain("- Den Abschnitt [X] gibt es in diesem Schritt nicht."));
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TheBuiltInRequestIsAPromptTheTestFolderMayReplace()
|
||||
{
|
||||
Assert.That(BflPromptLibrary.AllPaths, Does.Contain(BflPromptLibrary.WORK_STATE_PATH));
|
||||
}
|
||||
}
|
||||
@@ -61,10 +61,11 @@ public static partial class JsonTestRoots
|
||||
yield return Case(new(typeof(IReadOnlyList<string>), SqliteProvenanceStoreClientImplementation.JSON_OPTIONS));
|
||||
yield return Case(new(typeof(List<string>), SqliteProvenanceStoreClientImplementation.JSON_OPTIONS));
|
||||
|
||||
// The preparations of the BeyondFacts Lab, their forms and materials, and the preparation selected last:
|
||||
// The preparations of the BeyondFacts Lab, their forms, materials, and work states, and the preparation selected last:
|
||||
yield return Case(new(typeof(BflProject), BeyondFactsLabJson.PERSISTENCE));
|
||||
yield return Case(new(typeof(BflStepFormData), BeyondFactsLabJson.PERSISTENCE));
|
||||
yield return Case(new(typeof(BflMaterial), BeyondFactsLabJson.PERSISTENCE));
|
||||
yield return Case(new(typeof(BflWorkState), BeyondFactsLabJson.PERSISTENCE));
|
||||
yield return Case(new(typeof(Guid?), BeyondFactsLabJson.PERSISTENCE), "selected preparation");
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user