WIP (unstable): WebUntis-iCal-Abgleich für Vertretungen/Ausfälle
Erkennt Vertretungen, Ausfälle und Zusatzaufsichten aus dem persönlichen WebUntis-iCal-Feed und schreibt sie automatisch als SubstitutionEntry. Bekannter offener Bug: es tauchen weiterhin falsche Vertretungen für Stunden auf, die real unverändert sind — wird in einem Folge-Commit untersucht, deshalb vorerst auf diesem Branch statt main. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Core.Services;
|
||||
using LehrerApp.Desktop.Services;
|
||||
using LehrerApp.Desktop.ViewModels.Planning;
|
||||
using LehrerApp.Desktop.ViewModels.Settings;
|
||||
using Xunit;
|
||||
@@ -12,7 +13,8 @@ public sealed class TimetableViewModelTests
|
||||
FakeTimetableSlots slots, FakeGroups groups, FakeSchoolHolidays? holidays = null,
|
||||
FakeSubjects? subjects = null, FakeLessons? lessons = null, FakeExams? exams = null,
|
||||
SchoolCalendarSettingsService? calendarSettings = null,
|
||||
FakeSupervisionDuties? supervisionDuties = null, FakeSubstitutionEntries? substitutions = null)
|
||||
FakeSupervisionDuties? supervisionDuties = null, FakeSubstitutionEntries? substitutions = null,
|
||||
FakeUntisSlotMappings? untisMappings = null, WebUntisSettingsService? untisSettings = null)
|
||||
{
|
||||
// Bewusst kein "using": SchoolCalendarSettingsService liest den Pfad nur bei Bedarf
|
||||
// (SetState), das Verzeichnis muss über die Lebensdauer des ViewModels bestehen bleiben.
|
||||
@@ -25,7 +27,8 @@ public sealed class TimetableViewModelTests
|
||||
holidays ?? new FakeSchoolHolidays(),
|
||||
calendarSettings ?? new SchoolCalendarSettingsService(tempPath),
|
||||
new PublicHolidayService(), new SchoolYearService(),
|
||||
supervisionDuties ?? new FakeSupervisionDuties(), substitutions ?? new FakeSubstitutionEntries());
|
||||
supervisionDuties ?? new FakeSupervisionDuties(), substitutions ?? new FakeSubstitutionEntries(),
|
||||
untisMappings ?? new FakeUntisSlotMappings(), untisSettings ?? TestSupport.BuildWebUntisSettingsService());
|
||||
}
|
||||
|
||||
/// Nächstes Datum ab (inkl.) <paramref name="from"/>, das auf einen Wochentag Mo-Fr fällt —
|
||||
@@ -727,6 +730,31 @@ public sealed class TimetableViewModelTests
|
||||
Assert.Equal("Vertretung für Fr. Schmidt", mondayCell.SupervisionLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_ZusaetzlicheAufsichtInDerFolgewoche_ErscheintImWochenrasterNachNavigation()
|
||||
{
|
||||
// Nutzer-Feedback: "Auch die Extra-Aufsicht ist dann nicht im Plan [...] So macht doch der
|
||||
// Sync nur so halb Sinn" - dieselbe Anzeige-Infrastruktur wie beim Ausfall oben, hier für
|
||||
// eine automatisch erkannte zusätzliche Aufsicht ohne reguläre SupervisionDuty.
|
||||
var dateNextWeek = DateInCurrentWeek(DayOfWeek.Monday).AddDays(7);
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = dateNextWeek, Kind = SubstitutionKind.Supervision, AfterPeriod = 1,
|
||||
Description = "Automatisch über WebUntis-Abgleich erkannt: Zusätzliche Aufsicht laut WebUntis.",
|
||||
});
|
||||
var vm = BuildViewModel(new FakeTimetableSlots(), new FakeGroups([]), substitutions: substitutions);
|
||||
|
||||
Assert.DoesNotContain(vm.WeekItems, c => c.IsSupervisionRow && c.Text.Contains("n. 1."));
|
||||
|
||||
vm.NextWeekCommand.Execute(null);
|
||||
|
||||
var label = vm.WeekItems.Single(c => c.IsSupervisionRow && c.Text.Contains("n. 1."));
|
||||
var mondayCell = vm.WeekItems.SkipWhile(c => c != label).Skip(1).First();
|
||||
Assert.True(mondayCell.IsSubstitutionSupervision);
|
||||
Assert.Contains("Zusätzliche Aufsicht", mondayCell.SupervisionLocation);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_VertretungsstundeUeberschreibtNormaleAnzeige()
|
||||
{
|
||||
@@ -930,6 +958,38 @@ public sealed class TimetableViewModelTests
|
||||
Assert.Equal("6a auf Klassenfahrt", cell.Topic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_AusfallInDerFolgewoche_ErscheintImWochenrasterNachNavigation()
|
||||
{
|
||||
// Nutzer-Feedback: "Nächste Woche habe ich eine Stunde Ausfall [...] wäre es doch auch
|
||||
// schön, wenn das im Stundenplan für die nächste Woche irgendwie erkenntlich ist" - genau
|
||||
// der Weg, den ein automatisch von WebUntis erkannter Ausfall nimmt (derselbe
|
||||
// SubstitutionEntry-Mechanismus wie bei Handeinträgen), hier erstmals mit WeekOffset != 0
|
||||
// geprüft.
|
||||
var subject = new Subject { Name = "Naturwissenschaften", ShortName = "NAT" };
|
||||
var group = new LearningGroup { Name = "6a", SubjectId = subject.Id };
|
||||
var dateNextWeek = DateInCurrentWeek(DayOfWeek.Thursday).AddDays(7);
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Thursday, PeriodNumber = 3 });
|
||||
var substitutions = new FakeSubstitutionEntries();
|
||||
substitutions.Add(new SubstitutionEntry
|
||||
{
|
||||
Date = dateNextWeek, Kind = SubstitutionKind.Cancelled, PeriodNumber = 3,
|
||||
Description = "Automatisch über WebUntis-Abgleich erkannt.",
|
||||
});
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), subjects: new FakeSubjects([subject]), substitutions: substitutions);
|
||||
|
||||
// Aktuelle Woche: an dieser Stelle regulär, noch kein Ausfall sichtbar.
|
||||
var currentWeekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Thursday && c.PeriodNumber == 3);
|
||||
Assert.False(currentWeekCell.IsCancelled);
|
||||
|
||||
vm.NextWeekCommand.Execute(null);
|
||||
|
||||
var nextWeekCell = vm.WeekItems.Single(c => c.IsSlotCell && c.Weekday == DayOfWeek.Thursday && c.PeriodNumber == 3);
|
||||
Assert.True(nextWeekCell.IsCancelled);
|
||||
Assert.Equal("Automatisch über WebUntis-Abgleich erkannt.", nextWeekCell.Topic);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_Wochenraster_KeinAusfall_ZeigtNormaleStunde()
|
||||
{
|
||||
@@ -1041,4 +1101,79 @@ public sealed class TimetableViewModelTests
|
||||
|
||||
Assert.Single(vm.UpcomingExams);
|
||||
}
|
||||
|
||||
// ── WebUntis-Abweichung ───────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
public void Load_UntisAbgleichNichtAktiviert_ZeigtKeineAbweichung()
|
||||
{
|
||||
var group = new LearningGroup { Name = "10c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]));
|
||||
|
||||
Assert.False(vm.HasUntisMismatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_UntisAktivSlotOhneBestaetigteZuordnung_ZeigtAbweichung()
|
||||
{
|
||||
var group = new LearningGroup { Name = "10c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
var untisSettings = TestSupport.BuildWebUntisSettingsService();
|
||||
untisSettings.SetIcalUrl("https://example.org/ical");
|
||||
untisSettings.SetEnabled(true);
|
||||
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), untisSettings: untisSettings);
|
||||
|
||||
Assert.True(vm.HasUntisMismatch);
|
||||
Assert.Contains("1 Stundenplan-Eintrag", vm.UntisMismatchLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_UntisAktivMitBestaetigterZuordnung_ZeigtKeineAbweichung()
|
||||
{
|
||||
var group = new LearningGroup { Name = "10c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
var mappings = new FakeUntisSlotMappings();
|
||||
mappings.Add(new UntisSlotMapping
|
||||
{
|
||||
Weekday = DayOfWeek.Monday, PeriodNumber = 1, GroupId = group.Id, Confirmed = true,
|
||||
});
|
||||
var untisSettings = TestSupport.BuildWebUntisSettingsService();
|
||||
untisSettings.SetIcalUrl("https://example.org/ical");
|
||||
untisSettings.SetEnabled(true);
|
||||
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), untisMappings: mappings, untisSettings: untisSettings);
|
||||
|
||||
Assert.False(vm.HasUntisMismatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Load_DoppelstundenZuordnungBestaetigtBeideTimetableSlots()
|
||||
{
|
||||
// Regression: "ich habe aber jetzt alles zugeordnet, und trotzdem erhalte ich die
|
||||
// Warnung, dass 16 Stunden ohne Untis-Zuordnung sind" - eine WebUntis-Doppelstunde
|
||||
// bestätigt beide zugehörigen TimetableSlots, nicht nur den ersten.
|
||||
var group = new LearningGroup { Name = "10c" };
|
||||
var slots = new FakeTimetableSlots();
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
|
||||
slots.Add(new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
|
||||
var mappings = new FakeUntisSlotMappings();
|
||||
mappings.Add(new UntisSlotMapping
|
||||
{
|
||||
Weekday = DayOfWeek.Monday, PeriodNumber = 1, CoveredPeriods = [1, 2],
|
||||
GroupId = group.Id, Confirmed = true,
|
||||
});
|
||||
var untisSettings = TestSupport.BuildWebUntisSettingsService();
|
||||
untisSettings.SetIcalUrl("https://example.org/ical");
|
||||
untisSettings.SetEnabled(true);
|
||||
|
||||
var vm = BuildViewModel(slots, new FakeGroups([group]), untisMappings: mappings, untisSettings: untisSettings);
|
||||
|
||||
Assert.False(vm.HasUntisMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user