Files
LehrerApp/LehrerApp.Desktop.Tests/HazardAssessmentWizardViewModelTests.cs
T
adminandClaude Sonnet 5 7b883555bf feat: Chemikalien-Recherche über lokale RiSU-Stoffliste (kein KI-Aufruf mehr)
Ersetzt den ursprünglichen KI-Websuche-Ansatz (GESTIS scheiterte sowohl über
Websuche als auch über direkten API-Zugriff an fehlenden schulspezifischen
Tätigkeitsbeschränkungen) durch einen reinen lokalen Datenbank-Lookup gegen die
offizielle RiSU-Stoffliste (1764 Stoffe inkl. Tätigkeitsbeschränkungs-Codes,
Legende zur Abfragezeit übersetzt statt in die Datenbank gebacken). Keine
KI-Kosten, keine Drittanbieter-Abhängigkeit mehr für diese Funktion.

HazardSubstance.AgeRestriction zu ActivityRestriction umbenannt, da die
Tätigkeitsbeschränkung mehr abdeckt als reine Altersgrenzen. Details und
verworfene Zwischenstände in TODO.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 23:43:56 +02:00

398 lines
14 KiB
C#

using LehrerApp.Core.AiPlanning;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using Xunit;
namespace LehrerApp.Desktop.Tests;
public sealed class HazardAssessmentWizardViewModelTests
{
private static HazardAssessmentWizardViewModel Build(HazardAssessment? editing = null) => new(editing, "10c");
private static string BuildTempPath()
{
var path = Path.Combine(Path.GetTempPath(), $"lehrerapp-gbuwizard-tests-{Guid.NewGuid():N}");
Directory.CreateDirectory(path);
return path;
}
[Fact]
public void Neu_StartetAufSchritt0OhneKiOhneEditingKennzeichnung()
{
var vm = Build();
Assert.Equal(0, vm.StepIndex);
Assert.True(vm.IsBasicsStep);
Assert.True(vm.IsFirstStep);
Assert.Equal("Gefährdungsbeurteilung erstellen", vm.DialogTitle);
Assert.False(vm.CanUseAi);
}
[Fact]
public void Next_OhneTitel_BleibtAufSchritt0UndSetztFehler()
{
var vm = Build();
vm.NextCommand.Execute(null);
Assert.Equal(0, vm.StepIndex);
Assert.NotEqual("", vm.TitleError);
}
[Fact]
public void Next_MitTitel_GehtEinenSchrittWeiterUndAktualisiertStepFlags()
{
var vm = Build();
vm.Title = "Natrium in Wasser";
vm.NextCommand.Execute(null);
Assert.Equal(1, vm.StepIndex);
Assert.True(vm.IsSubstancesStep);
Assert.False(vm.IsFirstStep);
Assert.Equal("", vm.TitleError);
}
[Fact]
public void Back_AufSchritt0_BleibtAufSchritt0()
{
var vm = Build();
vm.BackCommand.Execute(null);
Assert.Equal(0, vm.StepIndex);
}
[Fact]
public void DurchlaufAllerSchritte_LandetAufZusammenfassung()
{
var vm = Build();
vm.Title = "Natrium in Wasser";
for (var i = 0; i < vm.StepCount - 1; i++) vm.NextCommand.Execute(null);
Assert.True(vm.IsSummaryStep);
Assert.True(vm.IsLastStep);
vm.NextCommand.Execute(null);
Assert.True(vm.IsSummaryStep, "Weiter auf dem letzten Schritt darf nicht darüber hinausgehen.");
}
[Fact]
public void AddSubstance_UndRemove_AktualisierenDieListeUndHasSubstances()
{
var vm = Build();
Assert.False(vm.HasSubstances);
vm.AddSubstanceCommand.Execute(null);
Assert.True(vm.HasSubstances);
var item = vm.Substances[0];
item.Name = "Natrium";
item.Amount = "ca. 0,5 g";
item.PictogramOptions.First(o => o.Value == GhsPictogram.Flammable).IsSelected = true;
item.RemoveCommand.Execute(null);
Assert.False(vm.HasSubstances);
Assert.Empty(vm.Substances);
}
[Fact]
public void AddHazard_TrimmtUndLeertDasEingabefeld()
{
var vm = Build();
vm.NewHazard = " Verätzungsgefahr ";
vm.AddHazardCommand.Execute(null);
Assert.Equal(["Verätzungsgefahr"], vm.Hazards);
Assert.Equal("", vm.NewHazard);
}
[Fact]
public void AddHazard_MitLeeremTextTutNichts()
{
var vm = Build();
vm.NewHazard = " ";
vm.AddHazardCommand.Execute(null);
Assert.Empty(vm.Hazards);
}
[Fact]
public void RemoveHazard_EntferntGenauDenGenanntenEintrag()
{
var vm = Build();
vm.NewHazard = "A"; vm.AddHazardCommand.Execute(null);
vm.NewHazard = "B"; vm.AddHazardCommand.Execute(null);
vm.RemoveHazardCommand.Execute("A");
Assert.Equal(["B"], vm.Hazards);
}
[Fact]
public void AddProtectiveMeasure_TrimmtUndLeertDasEingabefeld()
{
var vm = Build();
vm.NewProtectiveMeasure = " Schutzbrille ";
vm.AddProtectiveMeasureCommand.Execute(null);
Assert.Equal(["Schutzbrille"], vm.ProtectiveMeasures);
Assert.Equal("", vm.NewProtectiveMeasure);
}
[Fact]
public void Save_OhneTitel_SetztFehlerUndSpringtZurueckAufSchritt0()
{
var vm = Build();
vm.Title = "Etwas";
vm.NextCommand.Execute(null); // Schritt 1
vm.Title = " ";
vm.SaveCommand.Execute(null);
Assert.Null(vm.Result);
Assert.Equal(0, vm.StepIndex);
Assert.NotEqual("", vm.TitleError);
}
[Fact]
public void Save_BautVollstaendigesErgebnisAusAllenSchritten()
{
var vm = Build();
vm.Title = "Natrium in Wasser";
vm.GroupLabel = "10c";
vm.DateText = "12.09.2026";
vm.KindName = "Lehrerversuch";
vm.Procedure = "Kurzbeschreibung";
vm.AddSubstanceCommand.Execute(null);
vm.Substances[0].Name = "Natrium";
vm.Substances[0].Amount = "ca. 0,5 g";
vm.Substances[0].HStatements = "H260";
vm.Substances[0].PictogramOptions.First(o => o.Value == GhsPictogram.Flammable).IsSelected = true;
vm.NewHazard = "Verpuffung"; vm.AddHazardCommand.Execute(null);
vm.NewProtectiveMeasure = "Schutzbrille"; vm.AddProtectiveMeasureCommand.Execute(null);
vm.FirstAid = "Wasser spülen";
vm.Disposal = "Abreagieren lassen";
vm.Notes = "Hinweis";
vm.SaveCommand.Execute(null);
Assert.NotNull(vm.Result);
var result = vm.Result!;
Assert.Equal("Natrium in Wasser", result.Title);
Assert.Equal("10c", result.GroupLabel);
Assert.Equal(new DateOnly(2026, 9, 12), result.Date);
Assert.Equal(ExperimentKind.Lehrerversuch, result.Kind);
var substance = Assert.Single(result.Substances);
Assert.Equal("Natrium", substance.Name);
Assert.Equal([GhsPictogram.Flammable], substance.GhsPictograms);
Assert.Equal(["Verpuffung"], result.Hazards);
Assert.Equal(["Schutzbrille"], result.ProtectiveMeasures);
Assert.Equal("Wasser spülen", result.FirstAid);
Assert.Equal("Abreagieren lassen", result.Disposal);
Assert.Equal("Hinweis", result.Notes);
Assert.False(result.IsAiAssisted);
}
[Fact]
public void Konstruktor_MitEditingLaedtAlleFelderVor()
{
var editing = new HazardAssessment
{
Title = "Bestehende GBU",
GroupLabel = "9a",
Date = new DateOnly(2026, 1, 15),
Kind = ExperimentKind.Schuelerversuch,
Procedure = "Ablauf",
Substances = [new HazardSubstance { Name = "Salzsäure", GhsPictograms = [GhsPictogram.Corrosive] }],
Hazards = ["Verätzung"],
ProtectiveMeasures = ["Handschuhe"],
FirstAid = "Erste Hilfe Text",
Disposal = "Entsorgungstext",
Notes = "Notiz",
IsAiAssisted = true,
};
var vm = Build(editing);
Assert.Equal("Gefährdungsbeurteilung bearbeiten", vm.DialogTitle);
Assert.Equal("Bestehende GBU", vm.Title);
Assert.Equal("9a", vm.GroupLabel);
Assert.Equal("15.01.2026", vm.DateText);
Assert.Equal("Schülerversuch", vm.KindName);
Assert.Equal("Ablauf", vm.Procedure);
var substance = Assert.Single(vm.Substances);
Assert.Equal("Salzsäure", substance.Name);
Assert.True(substance.PictogramOptions.Single(o => o.Value == GhsPictogram.Corrosive).IsSelected);
Assert.Equal(["Verätzung"], vm.Hazards);
Assert.Equal(["Handschuhe"], vm.ProtectiveMeasures);
Assert.Equal("Erste Hilfe Text", vm.FirstAid);
Assert.Equal("Entsorgungstext", vm.Disposal);
Assert.Equal("Notiz", vm.Notes);
Assert.True(vm.IsAiAssisted);
}
[Fact]
public void CanUseAi_OhneKiAbhaengigkeiten_IstFalseUndSubstanzenKoennenNichtRecherchieren()
{
var vm = Build();
vm.AddSubstanceCommand.Execute(null);
Assert.False(vm.CanUseAi);
Assert.False(vm.Substances[0].CanResearch);
}
[Fact]
public void CanUseAi_MitAngemeldeterKiUndUnitUndLesson_IstTrueUndSubstanzenKoennenRecherchieren()
{
var aiSettings = new AiSettingsService(BuildTempPath());
aiSettings.SetEnabled(true);
aiSettings.SetCredentialsAndToken("sebastian", "token-abc");
var ai = new AiPlanningService(new HttpClient(), new FakeLessons(), new FakeGroups([]),
new FakeSubjects([]), new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
var unit = new Unit { Title = "Redoxreaktionen" };
var lesson = new Lesson { Topic = "Natrium in Wasser" };
var vm = new HazardAssessmentWizardViewModel(null, "10c", ai, aiSettings, unit, lesson);
vm.AddSubstanceCommand.Execute(null);
Assert.True(vm.CanUseAi);
Assert.True(vm.Substances[0].CanResearch);
}
}
public sealed class HazardSubstanceEditItemTests
{
[Fact]
public async Task Research_OhneOnResearch_TutNichtsUndBleibtOhneFehler()
{
var item = new HazardSubstanceEditItem { Name = "Natrium" };
await item.ResearchCommand.ExecuteAsync(null);
Assert.Equal("", item.ResearchError);
Assert.False(item.IsResearching);
}
[Fact]
public async Task Research_OhneNamen_SetztFehlerUndRuftDelegateNicht()
{
var called = false;
var item = new HazardSubstanceEditItem
{
Name = " ",
OnResearch = _ => { called = true; return Task.FromResult(new AiSubstanceResearchResponse()); },
};
await item.ResearchCommand.ExecuteAsync(null);
Assert.False(called);
Assert.Equal("Stoffname eingeben.", item.ResearchError);
}
[Fact]
public async Task Research_UebernimmtAlleFelderAusDerAntwort()
{
var item = new HazardSubstanceEditItem { Name = "Natrium" };
item.OnResearch = name =>
{
Assert.Equal("Natrium", name);
return Task.FromResult(new AiSubstanceResearchResponse
{
Name = "Natrium",
Cas = "7440-23-5",
SignalWord = "Gefahr",
HStatements = "H260",
PStatements = "P223, P231+P232",
ActivityRestriction = "Für Schüler ab der 5. Klasse freigegeben",
GhsPictograms = ["GHS02", "GHS05"],
});
};
await item.ResearchCommand.ExecuteAsync(null);
Assert.Equal("Natrium", item.Name);
Assert.Equal("7440-23-5", item.Cas);
Assert.Equal("Gefahr", item.SignalWord);
Assert.Equal("H260", item.HStatements);
Assert.Equal("P223, P231+P232", item.PStatements);
Assert.Equal("Für Schüler ab der 5. Klasse freigegeben", item.ActivityRestriction);
Assert.True(item.PictogramOptions.Single(o => o.Value == GhsPictogram.Flammable).IsSelected);
Assert.True(item.PictogramOptions.Single(o => o.Value == GhsPictogram.Corrosive).IsSelected);
Assert.False(item.PictogramOptions.Single(o => o.Value == GhsPictogram.Toxic).IsSelected);
Assert.False(item.IsResearching);
Assert.Equal("", item.ResearchError);
}
[Fact]
public async Task Research_BeiFehler_SetztResearchErrorUndBehaeltAlteFelderUnveraendert()
{
var item = new HazardSubstanceEditItem { Name = "Unbekannt", HStatements = "H999" };
item.OnResearch = _ => throw new AiBackendException("Nicht genügend KI-Guthaben. Bitte Guthaben aufladen.");
await item.ResearchCommand.ExecuteAsync(null);
Assert.Equal("Nicht genügend KI-Guthaben. Bitte Guthaben aufladen.", item.ResearchError);
Assert.Equal("H999", item.HStatements);
Assert.False(item.IsResearching);
}
}
public sealed class ExperimentKindDisplayTests
{
[Theory]
[InlineData(ExperimentKind.Lehrerversuch, "Lehrerversuch")]
[InlineData(ExperimentKind.Schuelerversuch, "Schülerversuch")]
[InlineData(ExperimentKind.Demonstrationsversuch, "Demonstrationsversuch")]
public void Label_UndFromLabel_SindZueinanderInvers(ExperimentKind kind, string label)
{
Assert.Equal(label, ExperimentKindDisplay.Label(kind));
Assert.Equal(kind, ExperimentKindDisplay.FromLabel(label));
}
[Fact]
public void FromLabel_UnbekanntFaelltAufLehrerversuchZurueck() =>
Assert.Equal(ExperimentKind.Lehrerversuch, ExperimentKindDisplay.FromLabel("Unbekannt"));
[Theory]
[InlineData("Schuelerversuch", ExperimentKind.Schuelerversuch)]
[InlineData("Demonstrationsversuch", ExperimentKind.Demonstrationsversuch)]
[InlineData("etwas anderes", ExperimentKind.Lehrerversuch)]
public void FromWireValue_LiestDenUnmarkiertenEnumNamenDerKiAntwort(string wire, ExperimentKind expected) =>
Assert.Equal(expected, ExperimentKindDisplay.FromWireValue(wire));
}
public sealed class GhsPictogramDisplayTests
{
[Theory]
[InlineData(GhsPictogram.Explosive, "GHS01")]
[InlineData(GhsPictogram.Corrosive, "GHS05")]
[InlineData(GhsPictogram.Environmental, "GHS09")]
public void Code_LiefertDenErwartetenGhsCode(GhsPictogram value, string code) =>
Assert.Equal(code, GhsPictogramDisplay.Code(value));
[Fact]
public void FromCode_IstFallUnabhaengigUndTrimmt() =>
Assert.Equal(GhsPictogram.Corrosive, GhsPictogramDisplay.FromCode(" ghs05 "));
[Fact]
public void FromCode_UnbekannterCodeLiefertNull() =>
Assert.Null(GhsPictogramDisplay.FromCode("GHS99"));
[Fact]
public void BuildOptions_MarkiertNurDieUebergebenenAlsAusgewaehlt()
{
var options = GhsPictogramDisplay.BuildOptions([GhsPictogram.Toxic, GhsPictogram.Flammable]);
Assert.Equal(9, options.Count);
Assert.True(options.Single(o => o.Value == GhsPictogram.Toxic).IsSelected);
Assert.True(options.Single(o => o.Value == GhsPictogram.Flammable).IsSelected);
Assert.False(options.Single(o => o.Value == GhsPictogram.Corrosive).IsSelected);
}
}