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); } }