Files
AI-Studio/app/Tests/Assistants/SWOTAnalysis/SwotAnalysisModerationTests.cs
T

209 lines
9.5 KiB
C#
Raw Normal View History

2026-10-28 08:51:30 +01:00
using System.Text.Json;
using AIStudio.Assistants.SWOTAnalysis;
namespace AIStudio.Tests.Assistants.SWOTAnalysis;
[TestFixture]
public sealed class SwotAnalysisModerationTests
{
[Test]
public void AValidAnswerIsAssembledIntoTheResult()
{
var parsed = SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(CreateModeration()), out var moderation, out var issue);
var result = SwotAnalysisModeration.Assemble(moderation);
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(parsed, Is.True);
Assert.That(issue, Is.Empty);
Assert.That(result.Strengths.Label, Is.EqualTo("Stärken"));
Assert.That(result.Strengths.Findings.Single().Summary, Is.EqualTo("Experienced team"));
Assert.That(result.Threats.Findings.Single().Explanation, Is.EqualTo("The material announces a funded market entrant."));
Assert.That(result.OverallAssessment.Paragraphs, Is.EqualTo(new[] { "The position is solid.", "The competitor is the main risk." }));
Assert.That(result.ModerationNotes.Items.Single().Change, Is.EqualTo("Merged two strengths about the team."));
Assert.That(result.PrioritizedActions.Items.Single().AddressedFactors, Is.EqualTo(new[] { "Experienced team", "Growing market" }));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void FactorIdsAreMatchedWithoutCaseSensitivity()
{
var moderation = CreateModeration();
moderation.PrioritizedActions.Items.Single().AddressedFactorIds = ["s1", "o1"];
var parsed = SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out var parsedModeration, out _);
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(parsed, Is.True);
Assert.That(SwotAnalysisModeration.Assemble(parsedModeration).PrioritizedActions.Items.Single().AddressedFactors, Is.EqualTo(new[] { "Experienced team", "Growing market" }));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void AFactorIdOfAnotherCategoryIsRejected()
{
var moderation = CreateModeration();
moderation.Strengths.Findings.Single().FactorId = "W2";
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
Assert.That(issue, Is.EqualTo("Finding 1 of strengths has the factor_id \"W2\", which does not start with \"S\"."));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void AFactorIdUsedTwiceIsRejected()
{
var moderation = CreateModeration();
moderation.Strengths.Findings.Add(Finding("s1", "Loyal customers", "The survey shows long customer relationships."));
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
Assert.That(issue, Does.Contain("\"s1\" is used more than once"));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void AnActionForAnUnknownFactorIsRejected()
{
var moderation = CreateModeration();
moderation.PrioritizedActions.Items.Single().AddressedFactorIds = ["S1", "X9"];
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
Assert.That(issue, Does.Contain("\"X9\""));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void AnEmptyOverallAssessmentIsRejected()
{
var moderation = CreateModeration();
moderation.OverallAssessment.Text = " ";
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(moderation), out _, out var issue), Is.False);
Assert.That(issue, Is.EqualTo("The field overall_assessment.text is empty."));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void NotesWithoutChangesExplainWhy()
{
var withoutReason = CreateModeration();
withoutReason.ModerationNotes.Items.Clear();
var withReason = CreateModeration();
withReason.ModerationNotes.Items.Clear();
withReason.ModerationNotes.EmptyMessage = "The four categories needed no changes.";
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(withoutReason), out _, out var issue), Is.False);
Assert.That(issue, Does.StartWith("moderation_notes has no items"));
Assert.That(SwotAnalysisModerationResponseParser.TryParse(JsonSerializer.Serialize(withReason), out _, out _), Is.True);
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void AnAnswerWithoutJsonNamesTheProblemForTheModel()
{
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
Assert.That(SwotAnalysisModerationResponseParser.TryParse("Here is my assessment.", out _, out var issue), Is.False);
Assert.That(issue, Does.Contain("no complete JSON object"));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void TheRequestHoldsTheFourCategoriesTheGoalAndTheResearch()
{
var request = SwotAnalysisModeration.BuildUserRequest(CreateCategories(), "Support the investment decision", string.Empty, "A market report documents increasing demand.");
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
foreach (var category in new[] { "strengths", "weaknesses", "opportunities", "threats" })
Assert.That(request, Does.Contain($"\"{category}\""));
Assert.That(request, Does.Contain("\"factor_id\": \"S1\""));
Assert.That(request, Does.Contain("\"factor_id\": \"O1\""));
Assert.That(request, Does.Contain("The material names no threats."));
Assert.That(request, Does.Match("(?s)<analysis-goal>\\s*Support the investment decision\\s*</analysis-goal>"));
Assert.That(request, Does.Match("(?s)<analysis-focus>\\s*No additional analysis focus was provided\\.\\s*</analysis-focus>"));
Assert.That(request, Does.Match("(?s)<shared-research>\\s*A market report documents increasing demand\\.\\s*</shared-research>"));
Assert.That(request, Does.Not.Contain("<analysis-subject>"), "The moderator works on the results, not on the material.");
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
[Test]
public void TheRequestOmitsEmptyResearch() =>
Assert.That(SwotAnalysisModeration.BuildUserRequest(CreateCategories(), "Support the investment decision", string.Empty), Does.Not.Contain("<shared-research>"));
[Test]
public void ThePromptAsksForTheWholeModeratorSchema()
{
var prompt = SwotAnalysisModeration.BuildSystemPrompt("Answer in German.");
2026-11-01 20:32:16 +01:00
using (Assert.EnterMultipleScope())
2026-10-28 08:51:30 +01:00
{
foreach (var key in new[] { "\"moderation_notes\"", "\"overall_assessment\"", "\"prioritized_actions\"", "\"addressed_factor_ids\"", "\"factor_id\"" })
Assert.That(prompt, Does.Contain(key));
Assert.That(prompt, Does.EndWith("Answer in German."));
2026-11-01 20:32:16 +01:00
}
2026-10-28 08:51:30 +01:00
}
private static Dictionary<SwotAnalysisCategory, SwotCategoryAnalysisResult> CreateCategories() => new()
{
[SwotAnalysisCategory.STRENGTHS] = new() { Findings = [new() { Summary = "Experienced team", Explanation = "The documents describe long employee tenure." }] },
[SwotAnalysisCategory.WEAKNESSES] = new() { Findings = [new() { Summary = "Legacy system", Explanation = "The documents name an unsupported application." }] },
[SwotAnalysisCategory.OPPORTUNITIES] = new() { Findings = [new() { Summary = "Growing market", Explanation = "The supplied forecast shows increasing demand." }] },
[SwotAnalysisCategory.THREATS] = new() { EmptyMessage = "The material names no threats." },
};
private static SwotModerationResult CreateModeration() => new()
{
MatrixHeading = "SWOT-Matrix",
InternalLabel = "Intern",
ExternalLabel = "Extern",
PositiveLabel = "Positiv",
NegativeLabel = "Negativ",
Strengths = Category("Stärken", Finding("S1", "Experienced team", "The documents describe long employee tenure.")),
Weaknesses = Category("Schwächen", Finding("W1", "Legacy system", "The documents name an unsupported application.")),
Opportunities = Category("Chancen", Finding("O1", "Growing market", "The supplied forecast shows increasing demand.")),
Threats = Category("Risiken", Finding("T1", "New competitor", "The material announces a funded market entrant.")),
ModerationNotes = new()
{
Label = "Abgleich der Quadranten",
Items = [new() { Change = "Merged two strengths about the team.", Reason = "Both described the same tenure." }],
},
OverallAssessment = new()
{
Label = "Gesamtbewertung",
Text = "The position is solid.\n\nThe competitor is the main risk.",
},
PrioritizedActions = new()
{
Label = "Maßnahmen",
Items = [new() { Action = "Prepare expansion", Rationale = "Use the team's experience to address demand.", AddressedFactorIds = ["S1", "O1"] }],
},
};
private static SwotModeratedCategory Category(string label, SwotModeratedFinding finding) => new()
{
Label = label,
Findings = [finding],
};
private static SwotModeratedFinding Finding(string factorId, string summary, string explanation) => new()
{
FactorId = factorId,
Summary = summary,
Explanation = explanation,
};
}