WIP (unstable): lokaler Cache für Klassenlehrer-Fehlzeiten & Klassenbucheinträge
Nutzer-Feedback: die WebUntis-Berichtszeilen sind starr genug für ein eigenes Datenmodell, Warnungen sollen sofort da sein statt bei jedem Öffnen neu abgerufen zu werden - vor allem darf derselbe Bericht nicht mehrfach pro Stunde abgerufen werden, nur weil die Ansicht mehrfach geöffnet wird (Sorge, bei WebUntis aufzufallen). Neue Modelle UntisAbsenceCacheEntry/UntisClassRegisterCacheEntry (1:1 zu den bestehenden DTOs) + UntisCacheFetchState, bewusst nicht synchronisiert (gleiches "kein db.OnChange"-Muster wie UntisSnapshotEntry/AnnualPlanEvent) - jedes Gerät ruft WebUntis selbst ab, die Zeilenzahl wächst übers Schuljahr gewollt an. UntisReportCacheService: festes heißes Fenster der letzten 14 Tage, höchstens stündlich automatisch aufgefrischt; alles Ältere gilt als endgültig und wird dauerhaft aus dem Cache bedient. Die Entscheidungslogik (Plan) ist als reine, ohne Repositories/HTTP testbare Funktion ausgelagert. Klassenlehrer-Ansichten nutzen den Cache-Service statt WebUntisIntegrationService direkt; ein zusätzlicher Button umgeht die Stundensperre bewusst für manuelle Abrufe. Noch nicht mit echtem WebUntis-Zugang gegengeprüft (siehe TODO.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1053,6 +1053,84 @@ public sealed class RepositoryTests
|
||||
Assert.Equal("fahrt", repo.GetByExternalId("fahrt")!.ExternalId);
|
||||
}
|
||||
|
||||
// ── UntisAbsenceCacheRepository / UntisClassRegisterCacheRepository / UntisCacheFetchStateRepository ──
|
||||
|
||||
[Fact]
|
||||
public void UntisAbsenceCacheRepository_GetByClassAndRange_FiltertKlasseUndDatumsbereich()
|
||||
{
|
||||
using var db = NewInMemoryContext();
|
||||
var repo = new UntisAbsenceCacheRepository(db);
|
||||
repo.InsertRange(
|
||||
[
|
||||
new UntisAbsenceCacheEntry { ClassName = "10c", StudentName = "Max", Date = 20260824 },
|
||||
new UntisAbsenceCacheEntry { ClassName = "10c", StudentName = "Max", Date = 20260901 },
|
||||
new UntisAbsenceCacheEntry { ClassName = "10d", StudentName = "Ben", Date = 20260824 },
|
||||
]);
|
||||
|
||||
var result = repo.GetByClassAndRange("10c", 20260801, 20260831);
|
||||
|
||||
Assert.Single(result);
|
||||
Assert.Equal(20260824, result[0].Date);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UntisAbsenceCacheRepository_ReplaceRange_ErsetztNurDenAngegebenenBereich()
|
||||
{
|
||||
using var db = NewInMemoryContext();
|
||||
var repo = new UntisAbsenceCacheRepository(db);
|
||||
repo.InsertRange(
|
||||
[
|
||||
new UntisAbsenceCacheEntry { ClassName = "10c", StudentName = "Alt", Date = 20260810 },
|
||||
new UntisAbsenceCacheEntry { ClassName = "10c", StudentName = "Alt", Date = 20260901 },
|
||||
]);
|
||||
|
||||
repo.ReplaceRange("10c", 20260801, 20260831,
|
||||
[new UntisAbsenceCacheEntry { ClassName = "10c", StudentName = "Neu", Date = 20260815 }]);
|
||||
|
||||
var inRange = repo.GetByClassAndRange("10c", 20260801, 20260831);
|
||||
var untouched = repo.GetByClassAndRange("10c", 20260901, 20260901);
|
||||
Assert.Equal("Neu", Assert.Single(inRange).StudentName);
|
||||
Assert.Single(untouched);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UntisClassRegisterCacheRepository_InsertUndGetByClassAndRange_RoundTrip()
|
||||
{
|
||||
using var db = NewInMemoryContext();
|
||||
var repo = new UntisClassRegisterCacheRepository(db);
|
||||
repo.InsertRange([new UntisClassRegisterCacheEntry
|
||||
{
|
||||
ClassName = "10c", Date = 20260824, StudentName = "Max", Subject = "Deu",
|
||||
TeacherUsername = "mueller", CategoryName = "Positiv",
|
||||
}]);
|
||||
|
||||
var result = Assert.Single(repo.GetByClassAndRange("10c", 20260801, 20260831));
|
||||
Assert.Equal("mueller", result.TeacherUsername);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UntisCacheFetchStateRepository_SaveUndGet_RoundTripJeKlasseUndArt()
|
||||
{
|
||||
using var db = NewInMemoryContext();
|
||||
var repo = new UntisCacheFetchStateRepository(db);
|
||||
var fetchedAt = new DateTime(2026, 8, 25, 10, 0, 0, DateTimeKind.Utc);
|
||||
repo.Save(new UntisCacheFetchState
|
||||
{
|
||||
ClassName = "10c", Kind = UntisCacheKind.Absences,
|
||||
HotWindowFetchedAt = fetchedAt, ColdCoverageStartDate = 20260101,
|
||||
});
|
||||
|
||||
var result = repo.Get("10c", UntisCacheKind.Absences);
|
||||
var missing = repo.Get("10c", UntisCacheKind.ClassRegister);
|
||||
|
||||
Assert.NotNull(result);
|
||||
// LiteDB rundet Kind Utc->Local, verschiebt dabei die Ticks um denselben Zeitpunkt zu
|
||||
// erhalten (siehe CLAUDE.md) - deshalb erst beide Seiten auf UTC normalisieren.
|
||||
Assert.Equal(fetchedAt.ToUniversalTime(), result!.HotWindowFetchedAt!.Value.ToUniversalTime());
|
||||
Assert.Equal(20260101, result.ColdCoverageStartDate);
|
||||
Assert.Null(missing);
|
||||
}
|
||||
|
||||
// ── WorkTaskRepository ────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user