using LehrerApp.Core.Models; using LehrerApp.Core.Services; using LehrerApp.Desktop.Services; using LehrerApp.Desktop.ViewModels.Planning; using Xunit; namespace LehrerApp.Desktop.Tests; public sealed class UntisMappingReviewDialogViewModelTests { private static string BuildTempPath() { var path = Path.Combine(Path.GetTempPath(), $"lehrerapp-untisreview-tests-{Guid.NewGuid():N}"); Directory.CreateDirectory(path); return path; } private static UntisSyncService BuildUntisSyncService(FakeUntisSlotMappings mappings) => new( new HttpClient(), new WebUntisSettingsService(BuildTempPath()), new FakeUntisSnapshots(), mappings, new FakeSubstitutionEntries(), new FakeGroups([]), new FakeTimetableSlots(), new FakeSupervisionDuties(), new FakeSchoolHolidays(), new PublicHolidayService(), new SchoolCalendarSettingsService(BuildTempPath()), new PeriodScheduleService(BuildTempPath()), new UntisMatchingService(), new UntisDiffService()); private static UntisMappingReviewDialogViewModel BuildDialogVm(FakeUntisSlotMappings mappings, List? groups = null) => new(BuildUntisSyncService(mappings), mappings, groups ?? [], new FakeSubjects([])); private static List Options(params LearningGroup[] groups) => groups.Select(g => new UntisGroupOption(g, null)).ToList(); private static UntisSlotMatch BuildLessonMatch(Guid? groupId, string classToken = "10c") => new() { Pattern = new UntisWeeklyPattern { Weekday = DayOfWeek.Monday, StartTime = new TimeOnly(7, 50), EndTime = new TimeOnly(9, 20), Summary = "SOL", ClassTokens = [classToken], OccurrenceCount = 40, }, PeriodNumber = 1, SuggestedGroupId = groupId, IsConfident = groupId is not null, }; private static UntisSlotMatch BuildSupervisionMatch() => new() { Pattern = new UntisWeeklyPattern { Weekday = DayOfWeek.Tuesday, StartTime = new TimeOnly(9, 20), EndTime = new TimeOnly(9, 40), Summary = null, ClassTokens = [], OccurrenceCount = 40, }, AfterPeriod = 2, }; [Fact] public void UntisMappingRow_VorschlagWirdVorausgewaehlt() { var group = new LearningGroup { Name = "10c" }; var row = new UntisMappingRow(BuildLessonMatch(group.Id), Options(group), existing: null); Assert.Equal(group.Id, row.SelectedGroup?.Group.Id); Assert.True(row.IsConfident); Assert.False(row.IsSupervisionCandidate); } [Fact] public void UntisMappingRow_OhneVorschlag_BleibtLeer() { var row = new UntisMappingRow(BuildLessonMatch(null), [], existing: null); Assert.Null(row.SelectedGroup); Assert.False(row.IsConfident); } [Fact] public void UntisGroupOption_GleicherNameVerschiedeneFaecher_ZeigtFachImDisplayLabel() { // Nutzer-Feedback: "Meine Klasse habe ich 3-mal. Ohne das Fach dabei, kann ich nicht // sicher die richtige Lerngruppe hier auswählen." var option = new UntisGroupOption(new LearningGroup { Name = "10c" }, "Chemie"); Assert.Equal("10c (Chemie)", option.DisplayLabel); } [Fact] public void UntisGroupOption_OhneFach_ZeigtNurDenNamen() { var option = new UntisGroupOption(new LearningGroup { Name = "10c" }, null); Assert.Equal("10c", option.DisplayLabel); } [Fact] public void UntisMappingRow_AufsichtsMuster_WirdAlsSupervisionCandidateErkanntUndKannAufgeloestWerden() { // Nutzer-Feedback: "Zwei Termine sind meine Aufsichten [...] vom Zeitraster und von der // Dauer her könnten die erfasst werden" - kein Klassenbezug, aber AfterPeriod auflösbar. var row = new UntisMappingRow(BuildSupervisionMatch(), [], existing: null); Assert.True(row.IsSupervisionCandidate); Assert.True(row.CanResolve); Assert.False(row.ConfirmAsSupervision); } [Fact] public void Save_SchreibtNurZeilenMitAusgewaehlterGruppeAlsBestaetigt() { var groupA = new LearningGroup { Name = "10c" }; var groupB = new LearningGroup { Name = "10d" }; var mappings = new FakeUntisSlotMappings(); var vm = BuildDialogVm(mappings, [groupA, groupB]); vm.Rows.Add(new UntisMappingRow(BuildLessonMatch(groupA.Id), Options(groupA, groupB), existing: null)); vm.Rows.Add(new UntisMappingRow(BuildLessonMatch(null, "9z"), Options(groupA, groupB), existing: null)); // ignoriert vm.SaveCommand.Execute(null); var saved = Assert.Single(mappings.GetAll()); Assert.True(saved.Confirmed); Assert.Equal(SubstitutionKind.Lesson, saved.Kind); Assert.Equal(groupA.Id, saved.GroupId); Assert.Equal(1, saved.PeriodNumber); } [Fact] public void Save_AufsichtBestaetigt_SchreibtSupervisionMapping() { var mappings = new FakeUntisSlotMappings(); var vm = BuildDialogVm(mappings); var row = new UntisMappingRow(BuildSupervisionMatch(), [], existing: null) { ConfirmAsSupervision = true }; vm.Rows.Add(row); vm.SaveCommand.Execute(null); var saved = Assert.Single(mappings.GetAll()); Assert.Equal(SubstitutionKind.Supervision, saved.Kind); Assert.Null(saved.GroupId); Assert.Equal(2, saved.AfterPeriod); Assert.True(saved.Confirmed); } [Fact] public void Save_ErneutesSpeichernDerselbenZeile_AktualisiertStattDupliziert() { // Regression für "Kann es sein, dass er meine Verbesserungen gar nicht einspeichert" - // wiederholtes Bestätigen desselben Slots darf keine zweite Zeile anlegen (sonst // ArgumentException in UntisDiffService.Diff beim nächsten Poll, siehe dortigen Test). var groupA = new LearningGroup { Name = "10c" }; var groupB = new LearningGroup { Name = "10d" }; var mappings = new FakeUntisSlotMappings(); var vm1 = BuildDialogVm(mappings, [groupA, groupB]); vm1.Rows.Add(new UntisMappingRow(BuildLessonMatch(groupA.Id), Options(groupA, groupB), existing: null)); vm1.SaveCommand.Execute(null); var firstSaved = Assert.Single(mappings.GetAll()); // Dialog erneut geöffnet - die bereits bestätigte Gruppe muss vorbefüllt sein. var existing = mappings.GetAll().Single(); var row2 = new UntisMappingRow(BuildLessonMatch(groupA.Id), Options(groupA, groupB), existing); Assert.Equal(groupA.Id, row2.SelectedGroup?.Group.Id); row2.SelectedGroup = Options(groupB).Single(); // Nutzer korrigiert var vm2 = BuildDialogVm(mappings, [groupA, groupB]); vm2.Rows.Add(row2); vm2.SaveCommand.Execute(null); var saved = Assert.Single(mappings.GetAll()); Assert.Equal(firstSaved.Id, saved.Id); Assert.Equal(groupB.Id, saved.GroupId); } [Fact] public void Save_NutzerAendertVorschlag_UebernimmtNeueAuswahl() { var groupA = new LearningGroup { Name = "10c" }; var groupB = new LearningGroup { Name = "10d" }; var mappings = new FakeUntisSlotMappings(); var vm = BuildDialogVm(mappings, [groupA, groupB]); var row = new UntisMappingRow(BuildLessonMatch(groupA.Id), Options(groupA, groupB), existing: null); row.SelectedGroup = Options(groupB).Single(); vm.Rows.Add(row); vm.SaveCommand.Execute(null); Assert.Equal(groupB.Id, Assert.Single(mappings.GetAll()).GroupId); } }