CI / build-and-test (push) Canceled after 0s
- UntisHubService.RecordRun: ein abgeschlossener Langzeit-Fehlzeitenabgleich schliesst die kurzfristige Kadenz derselben Gruppe automatisch mit ab (nicht umgekehrt). - Fehlzeitenabgleich-Dialog: neue "Uebernahme als"-ComboBox statt starrem Zielstatus, vorbelegt mit dem berechneten Vorschlag, aber frei aenderbar. - Neuer ai-backend-Endpunkt untis-status.php + AiPlanningService.RequestUntisStatusSuggestionsAsync: gebuendelter, anonymisierter KI-Statusvorschlag (nur Positions-Id + Rohsignale, nie Name/Klasse/ Datum), mit hartem Id-Mengen-Abgleich gegen Verwechslung. - Neue MCP-Tools (UntisComparisonTools): get_untis_hub_status, get_untis_absence_rows/ apply_untis_absence_status (anonymer Weg ueber ENr-Zuordnung) sowie get_named_untis_absence_pattern als bewusste, eng begrenzte Ausnahme (Name+Fehlzeiten fuer explizit angegebene Schueler-IDs, mit Bestaetigung ohne Sitzungsfreigabe - dafuer IMcpConfirmationService.ConfirmAsync um allowSessionTrust erweitert). - MapStatus/ENr-Zuordnung aus dem ViewModel in das neue, geteilte UntisLessonAbsenceHelper gezogen, damit Dialog und MCP-Tool nie unterschiedliche Statusvorschlaege berechnen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
143 lines
5.6 KiB
C#
143 lines
5.6 KiB
C#
using LehrerApp.Core.Models;
|
|
using LehrerApp.Core.Services;
|
|
using LehrerApp.Desktop.Services;
|
|
using Xunit;
|
|
|
|
namespace LehrerApp.Desktop.Tests;
|
|
|
|
public sealed class UntisHubServiceTests
|
|
{
|
|
private static readonly DateTime UtcNow = new(2026, 9, 10, 8, 0, 0, DateTimeKind.Utc);
|
|
|
|
private static LearningGroup Group(string name, int? webUntisLessonId, bool isActive = true) =>
|
|
new() { Name = name, WebUntisLessonId = webUntisLessonId, IsActive = isActive };
|
|
|
|
[Fact]
|
|
public void BuildRows_ProGruppeMitLessonId_ZweiFehlzeitenzeilenPlusDreiGlobaleZeilen()
|
|
{
|
|
var group = Group("9a", 42);
|
|
|
|
var rows = UntisHubService.BuildRows([group], [], UtcNow);
|
|
|
|
Assert.Equal(5, rows.Count);
|
|
Assert.Equal(2, rows.Count(r => r.GroupId == group.Id));
|
|
Assert.Contains(rows, r => r.Kind == UntisHubJobKind.FehlzeitenKurz && r.GroupId == group.Id);
|
|
Assert.Contains(rows, r => r.Kind == UntisHubJobKind.FehlzeitenLang && r.GroupId == group.Id);
|
|
Assert.Contains(rows, r => r.Kind == UntisHubJobKind.OffenePeriods && r.GroupId == null);
|
|
Assert.Contains(rows, r => r.Kind == UntisHubJobKind.Klassenbuchabgleich && r.GroupId == null);
|
|
Assert.Contains(rows, r => r.Kind == UntisHubJobKind.Hausaufgabenabgleich && r.GroupId == null);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRows_NieGeprueft_GiltAlsUeberfaellig()
|
|
{
|
|
var rows = UntisHubService.BuildRows([], [], UtcNow);
|
|
|
|
Assert.All(rows, r => Assert.Equal(UntisHubDueState.Overdue, r.DueState));
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRows_FehlzeitenKurzVorSechsTagen_GiltAlsOk()
|
|
{
|
|
var group = Group("9a", 42);
|
|
var state = new UntisHubJobState
|
|
{
|
|
Kind = UntisHubJobKind.FehlzeitenKurz, GroupId = group.Id, LastRunAt = UtcNow.AddDays(-6),
|
|
};
|
|
|
|
var rows = UntisHubService.BuildRows([group], [state], UtcNow);
|
|
|
|
var row = rows.Single(r => r.Kind == UntisHubJobKind.FehlzeitenKurz);
|
|
Assert.Equal(UntisHubDueState.Ok, row.DueState);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRows_FehlzeitenKurzVorSechzehnTagen_GiltAlsFaellig()
|
|
{
|
|
var group = Group("9a", 42);
|
|
var state = new UntisHubJobState
|
|
{
|
|
Kind = UntisHubJobKind.FehlzeitenKurz, GroupId = group.Id, LastRunAt = UtcNow.AddDays(-16),
|
|
};
|
|
|
|
var rows = UntisHubService.BuildRows([group], [state], UtcNow);
|
|
|
|
var row = rows.Single(r => r.Kind == UntisHubJobKind.FehlzeitenKurz);
|
|
Assert.Equal(UntisHubDueState.Due, row.DueState);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRows_FehlzeitenKurzVorZweiundzwanzigTagen_GiltAlsUeberfaellig()
|
|
{
|
|
var group = Group("9a", 42);
|
|
var state = new UntisHubJobState
|
|
{
|
|
Kind = UntisHubJobKind.FehlzeitenKurz, GroupId = group.Id, LastRunAt = UtcNow.AddDays(-22),
|
|
};
|
|
|
|
var rows = UntisHubService.BuildRows([group], [state], UtcNow);
|
|
|
|
var row = rows.Single(r => r.Kind == UntisHubJobKind.FehlzeitenKurz);
|
|
Assert.Equal(UntisHubDueState.Overdue, row.DueState);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRows_FehlzeitenLangHatDeutlichLaengereKadenzAlsKurz()
|
|
{
|
|
var group = Group("9a", 42);
|
|
var lastRunAt = UtcNow.AddDays(-40);
|
|
var states = new List<UntisHubJobState>
|
|
{
|
|
new() { Kind = UntisHubJobKind.FehlzeitenKurz, GroupId = group.Id, LastRunAt = lastRunAt },
|
|
new() { Kind = UntisHubJobKind.FehlzeitenLang, GroupId = group.Id, LastRunAt = lastRunAt },
|
|
};
|
|
|
|
var rows = UntisHubService.BuildRows([group], states, UtcNow);
|
|
|
|
Assert.Equal(UntisHubDueState.Overdue, rows.Single(r => r.Kind == UntisHubJobKind.FehlzeitenKurz).DueState);
|
|
Assert.Equal(UntisHubDueState.Ok, rows.Single(r => r.Kind == UntisHubJobKind.FehlzeitenLang).DueState);
|
|
}
|
|
|
|
[Fact]
|
|
public void BuildRows_GruppeOhneWebUntisLessonId_WirdVonAufrufseiteAusgeschlossen()
|
|
{
|
|
// GetRows() filtert vorab auf WebUntisLessonId != null (siehe UntisHubService.GetRows) -
|
|
// BuildRows selbst bekommt bereits nur die eligible-Gruppen übergeben.
|
|
var eligible = new List<LearningGroup> { Group("9a", 42) };
|
|
|
|
var rows = UntisHubService.BuildRows(eligible, [], UtcNow);
|
|
|
|
Assert.Equal(2, rows.Count(r => r.GroupId != null));
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordRun_FehlzeitenLang_SchliesstAuchFehlzeitenKurzDerselbenGruppeAb()
|
|
{
|
|
// Nutzer-Feedback: der Langzeit-Abgleich deckt das kurzfristige Zeitfenster als Teilmenge mit
|
|
// ab - ohne diese Kopplung bliebe die kurzfristige Kadenz trotz erledigtem Langzeit-Abgleich
|
|
// als fällig stehen.
|
|
var group = Group("9a", 42);
|
|
var jobStates = new FakeUntisHubJobStates();
|
|
var hub = new UntisHubService(new FakeGroups([group]), jobStates, new SchoolYearService());
|
|
|
|
hub.RecordRun(UntisHubJobKind.FehlzeitenLang, group.Id, "3 Fehlzeiten übernommen");
|
|
|
|
var kurzState = jobStates.Get(UntisHubJobKind.FehlzeitenKurz, group.Id);
|
|
var langState = jobStates.Get(UntisHubJobKind.FehlzeitenLang, group.Id);
|
|
Assert.NotNull(kurzState?.LastRunAt);
|
|
Assert.Equal(langState!.LastRunAt, kurzState!.LastRunAt);
|
|
}
|
|
|
|
[Fact]
|
|
public void RecordRun_FehlzeitenKurz_LaesstFehlzeitenLangUnangetastet()
|
|
{
|
|
var group = Group("9a", 42);
|
|
var jobStates = new FakeUntisHubJobStates();
|
|
var hub = new UntisHubService(new FakeGroups([group]), jobStates, new SchoolYearService());
|
|
|
|
hub.RecordRun(UntisHubJobKind.FehlzeitenKurz, group.Id, "keine Abweichungen");
|
|
|
|
Assert.Null(jobStates.Get(UntisHubJobKind.FehlzeitenLang, group.Id));
|
|
}
|
|
}
|