Files
adminandClaude Sonnet 5 15463bc292
CI / build-and-test (push) Canceled after 0s
"Vorgang": Fallmappe für Klassenbuch- und Dokumentationseinträge
Neuer 4. Tab in der Klassenlehreransicht: bündelt Titel, Beschreibung,
Schlagwörter, verknüpfte Dokumentation und angeheftete (eingefrorene)
WebUntis-Klassenbucheinträge zu einem laufenden Problem mit einer/einem
oder mehreren Schüler*innen. Anheften direkt aus dem Klassenbuch-Tab per
Rechtsklick. Sync-fähig nach dem bestehenden Documentation-Muster.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-01 22:42:06 +02:00

1253 lines
51 KiB
C#

using LehrerApp.Core.Models;
using LehrerApp.Data.Repositories;
using Xunit;
namespace LehrerApp.Data.Tests;
public sealed class RepositoryTests
{
private static LiteDbContext NewInMemoryContext() => new(new MemoryStream());
// ── StudentRepository ─────────────────────────────────────────────────────
[Fact]
public void StudentRepository_GetByGroup_FindetNurZugeordneteSchueler()
{
using var db = NewInMemoryContext();
var repo = new StudentRepository(db);
var groupId = Guid.NewGuid();
var inGroup = new Student { FirstName = "Anna", LastName = "Beispiel" };
var notInGroup = new Student { FirstName = "Ben", LastName = "Muster" };
repo.Save(inGroup);
repo.Save(notInGroup);
db.Memberships.Insert(new GroupMembership { StudentId = inGroup.Id, GroupId = groupId });
var result = repo.GetByGroup(groupId);
Assert.Single(result);
Assert.Equal(inGroup.Id, result[0].Id);
}
[Fact]
public void StudentRepository_GetAll_OhneInactiveFiltertDeaktivierteSchuelerAus()
{
using var db = NewInMemoryContext();
var repo = new StudentRepository(db);
repo.Save(new Student { FirstName = "Anna", LastName = "Aktiv", IsActive = true });
repo.Save(new Student { FirstName = "Ben", LastName = "Inaktiv", IsActive = false });
Assert.Single(repo.GetAll(includeInactive: false));
Assert.Equal(2, repo.GetAll(includeInactive: true).Count);
}
[Fact]
public void StudentRepository_Delete_LehntVerknuepfteDatenAb()
{
using var db = NewInMemoryContext();
var repo = new StudentRepository(db);
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
repo.Save(student);
db.Memberships.Insert(new GroupMembership { StudentId = student.Id, GroupId = Guid.NewGuid() });
db.Grades.Insert(new Grade { StudentId = student.Id, GroupId = Guid.NewGuid(), Value = "2" });
var references = repo.GetReferenceSummary(student.Id);
Assert.Equal(1, references.Memberships);
Assert.Equal(1, references.Grades);
Assert.Throws<InvalidOperationException>(() => repo.Delete(student.Id));
Assert.NotNull(repo.GetById(student.Id));
}
[Fact]
public void StudentRepository_Delete_EntferntUnverknuepftenSchueler()
{
using var db = NewInMemoryContext();
var repo = new StudentRepository(db);
var student = new Student { FirstName = "Anna", LastName = "Beispiel" };
repo.Save(student);
repo.Delete(student.Id);
Assert.Null(repo.GetById(student.Id));
}
// ── GroupRepository ───────────────────────────────────────────────────────
[Fact]
public void GroupRepository_Save_LehntUnbekanntesFachAb()
{
using var db = NewInMemoryContext();
var repo = new GroupRepository(db);
var group = new LearningGroup { Name = "Mathe G", SchoolYear = "2025/26", SubjectId = Guid.NewGuid() };
Assert.Throws<InvalidOperationException>(() => repo.Save(group));
}
[Fact]
public void GroupRepository_Save_AkzeptiertVorhandenesFach()
{
using var db = NewInMemoryContext();
var subjectRepo = new SubjectRepository(db);
var groupRepo = new GroupRepository(db);
var subject = new Subject { Name = "Mathematik" };
subjectRepo.Save(subject);
groupRepo.Save(new LearningGroup { Name = "Mathe G", SchoolYear = "2025/26", SubjectId = subject.Id });
Assert.NotNull(groupRepo.GetAll(includeInactive: true).FirstOrDefault(g => g.Name == "Mathe G"));
}
[Fact]
public void ArchivierteGruppe_BlockiertZentraleSchreibvorgaenge()
{
using var db = NewInMemoryContext();
var groupRepo = new GroupRepository(db);
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26", IsActive = true };
groupRepo.Save(group);
group.IsActive = false;
groupRepo.Save(group);
var membership = new GroupMembership { GroupId = group.Id, StudentId = Guid.NewGuid() };
var exam = new Exam { GroupId = group.Id, Title = "Test" };
Assert.Throws<InvalidOperationException>(() => new GroupMembershipRepository(db).Save(membership));
Assert.Throws<InvalidOperationException>(() => new ExamRepository(db).Save(exam));
Assert.Throws<InvalidOperationException>(() => new GradeRepository(db).Save(
new Grade { GroupId = group.Id, StudentId = Guid.NewGuid(), Value = "2" }));
Assert.Throws<InvalidOperationException>(() => new UnitRepository(db).Save(
new Unit { GroupId = group.Id, Title = "Einheit" }));
Assert.Throws<InvalidOperationException>(() => new TimetableSlotRepository(db).Save(
new TimetableSlot { GroupId = group.Id, Weekday = DayOfWeek.Monday, PeriodNumber = 1 }));
}
[Fact]
public void ArchivierteGruppe_MussVorKorrekturSeparatReaktiviertWerden()
{
using var db = NewInMemoryContext();
var repo = new GroupRepository(db);
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26", IsActive = true };
repo.Save(group);
group.IsActive = false;
repo.Save(group);
var archived = repo.GetById(group.Id)!;
archived.Name = "Geändert";
Assert.Throws<InvalidOperationException>(() => repo.Save(archived));
archived.IsActive = true;
Assert.Throws<InvalidOperationException>(() => repo.Save(archived));
archived.Name = "8a";
repo.Save(archived);
archived.Name = "8b";
repo.Save(archived);
Assert.Equal("8b", repo.GetById(group.Id)!.Name);
}
[Fact]
public void GroupRepository_Delete_LoeschtAlleAbhaengigenDatensaetzeKaskadierend()
{
using var db = NewInMemoryContext();
var groupRepo = new GroupRepository(db);
var groupId = Guid.NewGuid();
groupRepo.Save(new LearningGroup { Id = groupId, Name = "Mathe G", SchoolYear = "2025/26" });
var studentId = Guid.NewGuid();
var membershipId = Guid.NewGuid();
db.Memberships.Insert(new GroupMembership { Id = membershipId, StudentId = studentId, GroupId = groupId });
var examId = Guid.NewGuid();
var examResultId = Guid.NewGuid();
db.Exams.Insert(new Exam { Id = examId, GroupId = groupId });
db.ExamResults.Insert(new ExamResult { Id = examResultId, ExamId = examId, StudentId = studentId });
var gradeId = Guid.NewGuid();
db.Grades.Insert(new Grade { Id = gradeId, GroupId = groupId, StudentId = studentId });
var unitId = Guid.NewGuid();
var lessonInUnitId = Guid.NewGuid();
db.Units.Insert(new Unit { Id = unitId, GroupId = groupId });
db.Lessons.Insert(new Lesson { Id = lessonInUnitId, UnitId = unitId, GroupId = groupId });
var looseLessonId = Guid.NewGuid();
db.Lessons.Insert(new Lesson { Id = looseLessonId, UnitId = Guid.NewGuid(), GroupId = groupId });
var sessionId = Guid.NewGuid();
var entryId = Guid.NewGuid();
db.ParticipationSessions.Insert(new ParticipationSession { Id = sessionId, GroupId = groupId });
db.ParticipationEntries.Insert(new ParticipationEntry { Id = entryId, SessionId = sessionId, StudentId = studentId });
var aspectId = Guid.NewGuid();
db.ParticipationAspects.Insert(new ParticipationAspect { Id = aspectId, GroupId = groupId, Key = "quality" });
var reportGradeId = Guid.NewGuid();
db.ReportGrades.Insert(new ReportGrade { Id = reportGradeId, GroupId = groupId, StudentId = studentId });
var gradingSchemeId = Guid.NewGuid();
db.GradingSchemes.Insert(new GradingScheme { Id = gradingSchemeId, GroupId = groupId });
var sectionId = Guid.NewGuid();
db.ParticipationSections.Insert(new ParticipationSection { Id = sectionId, GroupId = groupId });
var seatingPlanId = Guid.NewGuid();
db.SeatingPlans.Insert(new SeatingPlan { Id = seatingPlanId, GroupId = groupId, Name = "Raumplan" });
var documentationId = Guid.NewGuid();
db.Documentation.Insert(new Documentation { Id = documentationId, GroupId = groupId, StudentId = studentId });
var taskId = Guid.NewGuid();
db.Tasks.Insert(new WorkTask { Id = taskId, GroupId = groupId });
var timeEntryId = Guid.NewGuid();
db.TimeEntries.Insert(new TimeEntry { Id = timeEntryId, GroupId = groupId, TaskId = taskId });
groupRepo.Delete(groupId);
Assert.Null(db.Groups.FindById(groupId));
Assert.Null(db.Memberships.FindById(membershipId));
Assert.Null(db.Exams.FindById(examId));
Assert.Null(db.ExamResults.FindById(examResultId));
Assert.Null(db.Grades.FindById(gradeId));
Assert.Null(db.Units.FindById(unitId));
Assert.Null(db.Lessons.FindById(lessonInUnitId));
Assert.Null(db.Lessons.FindById(looseLessonId));
Assert.Null(db.ParticipationSessions.FindById(sessionId));
Assert.Null(db.ParticipationEntries.FindById(entryId));
Assert.Null(db.ParticipationAspects.FindById(aspectId));
Assert.Null(db.ReportGrades.FindById(reportGradeId));
Assert.Null(db.GradingSchemes.FindById(gradingSchemeId));
Assert.Null(db.ParticipationSections.FindById(sectionId));
Assert.Null(db.SeatingPlans.FindById(seatingPlanId));
Assert.Null(db.Documentation.FindById(documentationId)?.GroupId);
Assert.Null(db.Tasks.FindById(taskId)?.GroupId);
Assert.Null(db.TimeEntries.FindById(timeEntryId)?.GroupId);
}
[Fact]
public void GroupRepository_Delete_LaesstDatenAndererGruppenUnberuehrt()
{
using var db = NewInMemoryContext();
var groupRepo = new GroupRepository(db);
var deletedGroupId = Guid.NewGuid();
var otherGroupId = Guid.NewGuid();
groupRepo.Save(new LearningGroup { Id = deletedGroupId, Name = "Löschen", SchoolYear = "2025/26" });
groupRepo.Save(new LearningGroup { Id = otherGroupId, Name = "Bleibt", SchoolYear = "2025/26" });
var otherExamId = Guid.NewGuid();
db.Exams.Insert(new Exam { Id = otherExamId, GroupId = otherGroupId });
groupRepo.Delete(deletedGroupId);
Assert.Null(db.Groups.FindById(deletedGroupId));
Assert.NotNull(db.Groups.FindById(otherGroupId));
Assert.NotNull(db.Exams.FindById(otherExamId));
}
// ── GroupMembershipRepository ────────────────────────────────────────────
[Fact]
public void SeatingPlanRepository_SpeichertMehrerePlaeneUndEindeutigeZuordnungen()
{
using var db = NewInMemoryContext();
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
new GroupRepository(db).Save(group);
var anna = new Student { FirstName = "Anna", LastName = "A" };
var ben = new Student { FirstName = "Ben", LastName = "B" };
db.Students.InsertBulk([anna, ben]);
db.Memberships.InsertBulk([
new GroupMembership { GroupId = group.Id, StudentId = anna.Id },
new GroupMembership { GroupId = group.Id, StudentId = ben.Id },
]);
var repo = new SeatingPlanRepository(db);
repo.Save(new SeatingPlan
{
GroupId = group.Id, Name = "Standard", Room = "B204", Rows = 2, Columns = 2,
Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = anna.Id }],
});
repo.Save(new SeatingPlan
{
GroupId = group.Id, Name = "Standard", Room = "Physik", Rows = 2, Columns = 3,
IsBoardAtBottom = true,
ColumnGapWidths = [0, 90],
Assignments = [new SeatAssignment { Row = 1, Column = 2, StudentId = ben.Id }],
});
var plans = repo.GetByGroup(group.Id);
Assert.Equal(2, plans.Count);
Assert.Contains(plans, p => p.Room == "B204" && p.Assignments.Single().StudentId == anna.Id);
Assert.Contains(plans, p => p.Room == "Physik" && p.IsBoardAtBottom
&& p.ColumnGapWidths.SequenceEqual([0, 90])
&& p.Assignments.Single().StudentId == ben.Id);
}
[Fact]
public void SeatingPlanRepository_LehntDoppeltZugeordnetenSchuelerAb()
{
using var db = NewInMemoryContext();
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
new GroupRepository(db).Save(group);
var student = new Student { FirstName = "Anna", LastName = "A" };
db.Students.Insert(student);
db.Memberships.Insert(new GroupMembership { GroupId = group.Id, StudentId = student.Id });
var plan = new SeatingPlan
{
GroupId = group.Id, Name = "Standard", Rows = 2, Columns = 2,
Assignments =
[
new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id },
new SeatAssignment { Row = 0, Column = 1, StudentId = student.Id },
],
};
Assert.Throws<InvalidOperationException>(() => new SeatingPlanRepository(db).Save(plan));
}
[Fact]
public void SeatingPlanRepository_FiltertAusgeblendetePlaetzeAusserhalbDesRasters()
{
using var db = NewInMemoryContext();
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
new GroupRepository(db).Save(group);
var plan = new SeatingPlan
{
GroupId = group.Id, Name = "Standard", Rows = 2, Columns = 2,
HiddenSeats =
[
new HiddenSeat { Row = 0, Column = 1 },
new HiddenSeat { Row = 5, Column = 5 }, // außerhalb des 2x2-Rasters
],
};
new SeatingPlanRepository(db).Save(plan);
var saved = db.SeatingPlans.FindById(plan.Id);
Assert.NotNull(saved);
var hidden = Assert.Single(saved.HiddenSeats);
Assert.Equal((0, 1), (hidden.Row, hidden.Column));
}
/// Ein ausgeblendeter Platz darf nie belegt sein - die Zuordnung eines Schülers ist wertvoller
/// als das Ausblenden. SeatingPlanTabViewModel verhindert das Belegen ausgeblendeter Plätze
/// bereits in der UI; diese Absicherung greift für Daten, die diesen Weg umgehen (z. B. Sync
/// von einem älteren Client).
[Fact]
public void SeatingPlanRepository_BlendetPlatzWiederEinWennErBelegtWird()
{
using var db = NewInMemoryContext();
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
new GroupRepository(db).Save(group);
var student = new Student { FirstName = "Anna", LastName = "A" };
db.Students.Insert(student);
db.Memberships.Insert(new GroupMembership { GroupId = group.Id, StudentId = student.Id });
var plan = new SeatingPlan
{
GroupId = group.Id, Name = "Standard", Rows = 2, Columns = 2,
HiddenSeats = [new HiddenSeat { Row = 0, Column = 0 }],
Assignments = [new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id }],
};
new SeatingPlanRepository(db).Save(plan);
var saved = db.SeatingPlans.FindById(plan.Id);
Assert.NotNull(saved);
Assert.Empty(saved.HiddenSeats);
Assert.Equal(student.Id, saved.Assignments.Single().StudentId);
}
[Fact]
public void SeatingPlanRepository_AktualisiertPlanMitFehlendemOptionalenRaum()
{
using var db = NewInMemoryContext();
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
new GroupRepository(db).Save(group);
var student = new Student { FirstName = "Anna", LastName = "A" };
db.Students.Insert(student);
db.Memberships.Insert(new GroupMembership { GroupId = group.Id, StudentId = student.Id });
var plan = new SeatingPlan
{
GroupId = group.Id,
Name = "Standard",
Room = null!,
Rows = 2,
Columns = 2,
};
db.SeatingPlans.Insert(plan);
plan.Assignments.Add(new SeatAssignment { Row = 0, Column = 0, StudentId = student.Id });
new SeatingPlanRepository(db).Save(plan);
var saved = db.SeatingPlans.FindById(plan.Id);
Assert.NotNull(saved);
Assert.True(string.IsNullOrEmpty(saved.Room));
Assert.Equal(student.Id, saved.Assignments.Single().StudentId);
// LiteDB may materialize the empty optional value as null again. A later
// drag/drop save must therefore remain safe as well.
new SeatingPlanRepository(db).Save(saved);
}
[Fact]
public void GroupMembershipRepository_Save_LehntZweiteZuordnungFuerGleichesPaarAb()
{
using var db = NewInMemoryContext();
var repo = new GroupMembershipRepository(db);
var studentId = Guid.NewGuid();
var groupId = Guid.NewGuid();
repo.Save(new GroupMembership { StudentId = studentId, GroupId = groupId });
var duplicate = new GroupMembership { StudentId = studentId, GroupId = groupId };
Assert.Throws<InvalidOperationException>(() => repo.Save(duplicate));
}
[Fact]
public void GroupMembershipRepository_Save_ErlaubtAktualisierenDerselbenZuordnung()
{
using var db = NewInMemoryContext();
var repo = new GroupMembershipRepository(db);
var membership = new GroupMembership { StudentId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Period = MembershipPeriod.FullYear };
repo.Save(membership);
membership.Period = MembershipPeriod.H1Only;
repo.Save(membership); // gleiche Id -> darf keine Ausnahme werfen
var reloaded = repo.GetByStudentAndGroup(membership.StudentId, membership.GroupId);
Assert.Equal(MembershipPeriod.H1Only, reloaded?.Period);
}
// ── ExamRepository ────────────────────────────────────────────────────────
[Fact]
public void ExamRepository_Delete_LoeschtZugehoerigeErgebnisseMit()
{
using var db = NewInMemoryContext();
var repo = new ExamRepository(db);
var exam = new Exam { GroupId = Guid.NewGuid() };
repo.Save(exam);
var resultId = Guid.NewGuid();
db.ExamResults.Insert(new ExamResult { Id = resultId, ExamId = exam.Id, StudentId = Guid.NewGuid() });
repo.Delete(exam.Id);
Assert.Null(repo.GetById(exam.Id));
Assert.Null(db.ExamResults.FindById(resultId));
}
[Fact]
public void ExamRepository_GetByGroup_SortiertAbsteigendNachDatum()
{
using var db = NewInMemoryContext();
var repo = new ExamRepository(db);
var groupId = Guid.NewGuid();
repo.Save(new Exam { GroupId = groupId, Title = "Früh", Date = new DateOnly(2025, 9, 1) });
repo.Save(new Exam { GroupId = groupId, Title = "Spät", Date = new DateOnly(2025, 11, 1) });
var result = repo.GetByGroup(groupId);
Assert.Equal("Spät", result[0].Title);
Assert.Equal("Früh", result[1].Title);
}
[Fact]
public void ExamRepository_GetAll_UeberAlleGruppenHinwegSortiertAufsteigendNachDatum()
{
using var db = NewInMemoryContext();
var repo = new ExamRepository(db);
repo.Save(new Exam { GroupId = Guid.NewGuid(), Title = "Spät", Date = new DateOnly(2025, 11, 1) });
repo.Save(new Exam { GroupId = Guid.NewGuid(), Title = "Früh", Date = new DateOnly(2025, 9, 1) });
var result = repo.GetAll();
Assert.Equal("Früh", result[0].Title);
Assert.Equal("Spät", result[1].Title);
}
// ── SubjectRepository ─────────────────────────────────────────────────────
[Fact]
public void SubjectRepository_Save_TrimmtNamenUndKuerzel()
{
using var db = NewInMemoryContext();
var repo = new SubjectRepository(db);
var subject = new Subject { Name = " Chemie ", ShortName = " Ch " };
repo.Save(subject);
var reloaded = repo.GetById(subject.Id);
Assert.Equal("Chemie", reloaded?.Name);
Assert.Equal("Ch", reloaded?.ShortName);
}
[Fact]
public void SubjectRepository_Save_LehntLeerenNamenAb()
{
using var db = NewInMemoryContext();
var repo = new SubjectRepository(db);
Assert.Throws<ArgumentException>(() => repo.Save(new Subject { Name = " " }));
}
[Fact]
public void SubjectRepository_Save_LehntDuplikatUnabhaengigVonGrossKleinschreibungAb()
{
using var db = NewInMemoryContext();
var repo = new SubjectRepository(db);
repo.Save(new Subject { Name = "Chemie" });
Assert.Throws<InvalidOperationException>(() => repo.Save(new Subject { Name = "chemie" }));
}
[Fact]
public void SubjectRepository_Delete_WirdBlockiertWennGruppeDasFachVerwendet()
{
using var db = NewInMemoryContext();
var subjectRepo = new SubjectRepository(db);
var subject = new Subject { Name = "Chemie" };
subjectRepo.Save(subject);
db.Groups.Insert(new LearningGroup { Name = "Chemie G", SchoolYear = "2025/26", SubjectId = subject.Id });
Assert.Throws<InvalidOperationException>(() => subjectRepo.Delete(subject.Id));
}
[Fact]
public void SubjectRepository_Delete_FunktioniertWennUnbenutzt()
{
using var db = NewInMemoryContext();
var repo = new SubjectRepository(db);
var subject = new Subject { Name = "Chemie" };
repo.Save(subject);
repo.Delete(subject.Id);
Assert.Null(repo.GetById(subject.Id));
}
// ── ParticipationAspectRepository (3.1) ───────────────────────────────────
[Fact]
public void ParticipationAspectRepository_Save_LehntLeerenSchluesselUndBezeichnungAb()
{
using var db = NewInMemoryContext();
var repo = new ParticipationAspectRepository(db);
var groupId = Guid.NewGuid();
Assert.Throws<ArgumentException>(() => repo.Save(new ParticipationAspect { GroupId = groupId, Key = " ", Label = "Experiment" }));
Assert.Throws<ArgumentException>(() => repo.Save(new ParticipationAspect { GroupId = groupId, Key = "experiment", Label = " " }));
}
[Fact]
public void ParticipationAspectRepository_Save_LehntDuplikatSchluesselInnerhalbDerGruppeAb()
{
using var db = NewInMemoryContext();
var repo = new ParticipationAspectRepository(db);
var groupId = Guid.NewGuid();
repo.Save(new ParticipationAspect { GroupId = groupId, Key = "experiment", Label = "Experiment" });
Assert.Throws<InvalidOperationException>(() =>
repo.Save(new ParticipationAspect { GroupId = groupId, Key = "Experiment", Label = "Anderes Label" }));
}
[Fact]
public void ParticipationAspectRepository_Save_LehntDuplikatGegenGlobalenStandardAb()
{
using var db = NewInMemoryContext();
var repo = new ParticipationAspectRepository(db);
var groupId = Guid.NewGuid();
repo.Save(new ParticipationAspect { GroupId = null, Key = "quality", Label = "Qualität" });
// Gleicher Schlüssel wie ein globaler Standardaspekt wäre in der Bewertungsübersicht
// dieser Gruppe eine mehrdeutige Spalte (siehe ParticipationTabViewModel.LoadAspects).
Assert.Throws<InvalidOperationException>(() =>
repo.Save(new ParticipationAspect { GroupId = groupId, Key = "quality", Label = "Andere Qualität" }));
}
[Fact]
public void ParticipationAspectRepository_GetByGroup_FiltertInaktiveAus_GetAllByGroupNicht()
{
using var db = NewInMemoryContext();
var repo = new ParticipationAspectRepository(db);
var groupId = Guid.NewGuid();
repo.Save(new ParticipationAspect { GroupId = groupId, Key = "a", Label = "Aktiv", IsActive = true });
repo.Save(new ParticipationAspect { GroupId = groupId, Key = "b", Label = "Inaktiv", IsActive = false });
Assert.Single(repo.GetByGroup(groupId));
Assert.Equal(2, repo.GetAllByGroup(groupId).Count);
}
// ── GradingSchemeRepository ───────────────────────────────────────────────
[Fact]
public void GradingSchemeRepository_UnterscheidetGruppenspezifischesSchemaVonVoreinstellung()
{
using var db = NewInMemoryContext();
var repo = new GradingSchemeRepository(db);
var groupId = Guid.NewGuid();
repo.Save(new GradingScheme { GroupId = groupId, ExamsPercent = 60, ParticipationPercent = 30, OtherPercent = 10 });
repo.Save(new GradingScheme { GroupType = GroupType.Class, ExamsPercent = 50, ParticipationPercent = 40, OtherPercent = 10 });
var forGroup = repo.GetByGroup(groupId);
var defaultForClass = repo.GetDefaultForType(GroupType.Class);
var defaultForCourse = repo.GetDefaultForType(GroupType.Course);
Assert.Equal(60, forGroup?.ExamsPercent);
Assert.Equal(50, defaultForClass?.ExamsPercent);
Assert.Null(defaultForCourse);
}
// ── ReportGradeRepository ─────────────────────────────────────────────────
[Fact]
public void ReportGradeRepository_GetByStudentGroupPeriod_UnterscheidetZeitraeume()
{
using var db = NewInMemoryContext();
var repo = new ReportGradeRepository(db);
var studentId = Guid.NewGuid();
var groupId = Guid.NewGuid();
repo.Save(new ReportGrade { StudentId = studentId, GroupId = groupId, Period = "1. Halbjahr", CalculatedValue = "2" });
repo.Save(new ReportGrade { StudentId = studentId, GroupId = groupId, Period = "2. Halbjahr", CalculatedValue = "3" });
var h1 = repo.GetByStudentGroupPeriod(studentId, groupId, "1. Halbjahr");
var h2 = repo.GetByStudentGroupPeriod(studentId, groupId, "2. Halbjahr");
var unbekannt = repo.GetByStudentGroupPeriod(studentId, groupId, "Gesamtes Schuljahr");
Assert.Equal("2", h1?.CalculatedValue);
Assert.Equal("3", h2?.CalculatedValue);
Assert.Null(unbekannt);
}
// ── ParticipationSessionRepository ───────────────────────────────────────
[Fact]
public void ParticipationSessionRepository_Delete_LoeschtZugehoerigeEintraegeMit()
{
using var db = NewInMemoryContext();
var repo = new ParticipationSessionRepository(db);
var session = new ParticipationSession { GroupId = Guid.NewGuid() };
repo.Save(session);
var entryId = Guid.NewGuid();
db.ParticipationEntries.Insert(new ParticipationEntry { Id = entryId, SessionId = session.Id, StudentId = Guid.NewGuid() });
repo.Delete(session.Id);
Assert.Null(repo.GetById(session.Id));
Assert.Null(db.ParticipationEntries.FindById(entryId));
}
[Fact]
public void ParticipationEntry_EindeutigerIndex_LehntZweitenEintragFuerGleicheSitzungUndSchuelerAb()
{
using var db = NewInMemoryContext();
var repo = new ParticipationRepository(db);
var sessionId = Guid.NewGuid();
var studentId = Guid.NewGuid();
repo.Save(new ParticipationEntry { SessionId = sessionId, StudentId = studentId });
Assert.Throws<LiteDB.LiteException>(() =>
db.ParticipationEntries.Insert(new ParticipationEntry { SessionId = sessionId, StudentId = studentId }));
}
// ── DocumentationRepository ───────────────────────────────────────────────
[Fact]
public void DocumentationRepository_Delete_MarkiertNurAlsGeloescht()
{
using var db = NewInMemoryContext();
var repo = new DocumentationRepository(db);
var doc = new Documentation { StudentId = Guid.NewGuid(), Title = "Gespräch" };
repo.Save(doc);
repo.Delete(doc.Id);
Assert.Empty(repo.GetByStudent(doc.StudentId));
var raw = db.Documentation.FindById(doc.Id);
Assert.NotNull(raw);
Assert.True(raw.IsDeleted);
Assert.NotNull(raw.DeletedAt);
}
[Fact]
public void DocumentationRepository_HardDelete_EntferntDenEintragEndgueltig()
{
using var db = NewInMemoryContext();
var repo = new DocumentationRepository(db);
var doc = new Documentation { StudentId = Guid.NewGuid(), Title = "Vorkommnis" };
repo.Save(doc);
repo.HardDelete(doc.Id);
Assert.Null(db.Documentation.FindById(doc.Id));
}
[Fact]
public void DocumentationRepository_GetAll_FiltertGeloeschteEintraegeUndUmfasstAlleSchueler()
{
using var db = NewInMemoryContext();
var repo = new DocumentationRepository(db);
var visible = new Documentation { StudentId = Guid.NewGuid(), Title = "Sichtbar" };
var deleted = new Documentation { StudentId = Guid.NewGuid(), Title = "Gelöscht" };
repo.Save(visible);
repo.Save(deleted);
repo.Delete(deleted.Id);
var all = repo.GetAll();
Assert.Single(all);
Assert.Equal(visible.Id, all[0].Id);
}
[Fact]
public void DocumentationRepository_HardDelete_EntferntAuchDieAnhaenge()
{
using var db = NewInMemoryContext();
var repo = new DocumentationRepository(db);
var storage = new LiteAttachmentStorage(db);
string attachmentId;
using (var ms = new MemoryStream([1, 2, 3]))
attachmentId = storage.Upload("brief.pdf", ms);
var doc = new Documentation { StudentId = Guid.NewGuid(), Title = "Elternbrief" };
doc.Attachments.Add(new DocumentAttachment { StorageId = attachmentId, FileName = "brief.pdf", SizeBytes = 3 });
repo.Save(doc);
repo.HardDelete(doc.Id);
Assert.Null(db.Documentation.FindById(doc.Id));
Assert.Null(storage.OpenRead(attachmentId));
}
// ── VorgangRepository ─────────────────────────────────────────────────────
[Fact]
public void VorgangRepository_Save_SetztUpdatedAt()
{
using var db = NewInMemoryContext();
var repo = new VorgangRepository(db);
var vorgang = new Vorgang { Title = "Schuleschwänzen Max" };
repo.Save(vorgang);
var raw = db.Vorgaenge.FindById(vorgang.Id);
Assert.NotNull(raw);
Assert.True((DateTime.UtcNow - raw!.UpdatedAt).TotalSeconds < 5);
}
[Fact]
public void VorgangRepository_GetByStudent_FindetVorgaengeMitMehrerenSchuelern()
{
using var db = NewInMemoryContext();
var repo = new VorgangRepository(db);
var ada = Guid.NewGuid();
var ben = Guid.NewGuid();
var other = Guid.NewGuid();
var shared = new Vorgang { Title = "Konflikt", StudentIds = [ada, ben] };
var unrelated = new Vorgang { Title = "Anderes", StudentIds = [other] };
repo.Save(shared);
repo.Save(unrelated);
Assert.Equal([shared.Id], repo.GetByStudent(ada).Select(v => v.Id));
Assert.Equal([shared.Id], repo.GetByStudent(ben).Select(v => v.Id));
Assert.Empty(repo.GetByStudent(Guid.NewGuid()));
}
[Fact]
public void VorgangRepository_Delete_MarkiertNurAlsGeloescht()
{
using var db = NewInMemoryContext();
var repo = new VorgangRepository(db);
var vorgang = new Vorgang { Title = "Hausaufgaben fehlen" };
repo.Save(vorgang);
repo.Delete(vorgang.Id);
Assert.Empty(repo.GetAll());
Assert.Null(repo.GetById(vorgang.Id));
var raw = db.Vorgaenge.FindById(vorgang.Id);
Assert.NotNull(raw);
Assert.True(raw!.IsDeleted);
Assert.NotNull(raw.DeletedAt);
}
[Fact]
public void VorgangRepository_GetById_LiefertNullFuerUnbekannteId()
{
using var db = NewInMemoryContext();
var repo = new VorgangRepository(db);
Assert.Null(repo.GetById(Guid.NewGuid()));
}
// ── LessonRepository ──────────────────────────────────────────────────────
[Fact]
public void LessonRepository_GetByUnit_SortiertNachDatumDannStundennummer()
{
using var db = NewInMemoryContext();
var repo = new LessonRepository(db);
var unitId = Guid.NewGuid();
var groupId = Guid.NewGuid();
// Doppelstunde am selben Tag: Stunde 2 vor Stunde 1 eingefügt, muss trotzdem
// nach LessonNumber sortiert erscheinen.
repo.Save(new Lesson { UnitId = unitId, GroupId = groupId, Date = new DateOnly(2025, 9, 8), LessonNumber = 2 });
repo.Save(new Lesson { UnitId = unitId, GroupId = groupId, Date = new DateOnly(2025, 9, 8), LessonNumber = 1 });
repo.Save(new Lesson { UnitId = unitId, GroupId = groupId, Date = new DateOnly(2025, 9, 1), LessonNumber = 1 });
var result = repo.GetByUnit(unitId);
Assert.Equal(new DateOnly(2025, 9, 1), result[0].Date);
Assert.Equal(new DateOnly(2025, 9, 8), result[1].Date);
Assert.Equal(1, result[1].LessonNumber);
Assert.Equal(new DateOnly(2025, 9, 8), result[2].Date);
Assert.Equal(2, result[2].LessonNumber);
}
[Fact]
public void LessonRepository_DeleteRaeumtAnhaengeAusDerAttachmentAblageAuf()
{
using var db = NewInMemoryContext();
var repo = new LessonRepository(db);
var lesson = new Lesson
{
UnitId = Guid.NewGuid(), GroupId = Guid.NewGuid(),
Attachments = [new DocumentAttachment { StorageId = "gbu-1", FileName = "gbu.pdf" }],
};
db.Attachments.Upload("gbu-1", "gbu.pdf", new MemoryStream([1, 2, 3]));
repo.Save(lesson);
repo.Delete(lesson.Id);
Assert.Null(db.Lessons.FindById(lesson.Id));
Assert.False(db.Attachments.Exists("gbu-1"));
}
// ── ShorthandCodeRepository ───────────────────────────────────────────────
[Fact]
public void ShorthandCodeRepository_Save_LehntDuplikatCodeAb()
{
using var db = NewInMemoryContext();
var repo = new ShorthandCodeRepository(db);
repo.Save(new ShorthandCode { Code = "Tb", Label = "Tafelbild" });
Assert.Throws<InvalidOperationException>(() =>
repo.Save(new ShorthandCode { Code = "tb", Label = "Anderes Tafelbild" }));
}
[Fact]
public void ShorthandCodeRepository_GetAll_SortiertNachCode()
{
using var db = NewInMemoryContext();
var repo = new ShorthandCodeRepository(db);
repo.Save(new ShorthandCode { Code = "SH", Label = "Schülerheft" });
repo.Save(new ShorthandCode { Code = "AB", Label = "Arbeitsblatt" });
var result = repo.GetAll();
Assert.Equal(["AB", "SH"], result.Select(c => c.Code));
}
[Fact]
public void ShorthandCodeRepository_Delete_EntferntEintrag()
{
using var db = NewInMemoryContext();
var repo = new ShorthandCodeRepository(db);
repo.Save(new ShorthandCode { Code = "Tb", Label = "Tafelbild" });
var id = repo.GetAll().Single().Id;
repo.Delete(id);
Assert.Empty(repo.GetAll());
}
// ── AlternativeLessonPathRepository ───────────────────────────────────────
[Fact]
public void AlternativeLessonPathRepository_Save_LehntDuplikatNamenAb()
{
using var db = NewInMemoryContext();
var repo = new AlternativeLessonPathRepository(db);
repo.Save(new AlternativeLessonPath { Name = "Kurzversion" });
Assert.Throws<InvalidOperationException>(() =>
repo.Save(new AlternativeLessonPath { Name = "kurzversion" }));
}
[Fact]
public void AlternativeLessonPathRepository_GetById_FindetGespeichertenEintragMitBeschreibung()
{
using var db = NewInMemoryContext();
var repo = new AlternativeLessonPathRepository(db);
var entry = new AlternativeLessonPath { Name = "Kurzversion", Description = "Bei Zeitmangel." };
repo.Save(entry);
var found = repo.GetById(entry.Id);
Assert.NotNull(found);
Assert.Equal("Kurzversion", found!.Name);
Assert.Equal("Bei Zeitmangel.", found.Description);
}
[Fact]
public void AlternativeLessonPathRepository_Delete_EntferntEintrag()
{
using var db = NewInMemoryContext();
var repo = new AlternativeLessonPathRepository(db);
repo.Save(new AlternativeLessonPath { Name = "Kurzversion" });
var id = repo.GetAll().Single().Id;
repo.Delete(id);
Assert.Empty(repo.GetAll());
}
// ── TimetableSlotRepository ───────────────────────────────────────────────
[Fact]
public void TimetableSlotRepository_GetByGroup_FindetNurSlotsDerGruppe()
{
using var db = NewInMemoryContext();
var repo = new TimetableSlotRepository(db);
var groupA = Guid.NewGuid();
var groupB = Guid.NewGuid();
repo.Save(new TimetableSlot { GroupId = groupA, Weekday = DayOfWeek.Monday, PeriodNumber = 1 });
repo.Save(new TimetableSlot { GroupId = groupB, Weekday = DayOfWeek.Monday, PeriodNumber = 2 });
var result = repo.GetByGroup(groupA);
Assert.Single(result);
Assert.Equal(groupA, result[0].GroupId);
}
[Fact]
public void TimetableSlotRepository_Save_LehntDoppelbelegungDerselbenStundeAb()
{
using var db = NewInMemoryContext();
var repo = new TimetableSlotRepository(db);
repo.Save(new TimetableSlot { GroupId = Guid.NewGuid(), Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 });
Assert.Throws<InvalidOperationException>(() =>
repo.Save(new TimetableSlot { GroupId = Guid.NewGuid(), Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 }));
}
[Fact]
public void TimetableSlotRepository_Save_AktualisierenDesselbenSlotsIstErlaubt()
{
using var db = NewInMemoryContext();
var repo = new TimetableSlotRepository(db);
var slot = new TimetableSlot { GroupId = Guid.NewGuid(), Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 };
repo.Save(slot);
slot.Room = "R204";
repo.Save(slot);
Assert.Equal("R204", repo.GetAll().Single().Room);
}
// ── SchoolHolidayRepository ───────────────────────────────────────────────
[Fact]
public void SchoolHolidayRepository_GetAll_SortiertNachStartdatum()
{
using var db = NewInMemoryContext();
var repo = new SchoolHolidayRepository(db);
repo.Save(new SchoolHoliday { Name = "Sommerferien", StartDate = new DateOnly(2026, 7, 1), EndDate = new DateOnly(2026, 8, 10) });
repo.Save(new SchoolHoliday { Name = "Osterferien", StartDate = new DateOnly(2026, 3, 30), EndDate = new DateOnly(2026, 4, 10) });
var result = repo.GetAll();
Assert.Equal("Osterferien", result[0].Name);
Assert.Equal("Sommerferien", result[1].Name);
}
// ── SupervisionDutyRepository ─────────────────────────────────────────────
[Fact]
public void SupervisionDutyRepository_Save_LehntDoppelbelegungDerselbenPauseAb()
{
using var db = NewInMemoryContext();
var repo = new SupervisionDutyRepository(db);
repo.Save(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" });
Assert.Throws<InvalidOperationException>(() =>
repo.Save(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Bibliothek" }));
}
[Fact]
public void SupervisionDutyRepository_Save_AktualisierenDerselbenAufsichtIstErlaubt()
{
using var db = NewInMemoryContext();
var repo = new SupervisionDutyRepository(db);
var duty = new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" };
repo.Save(duty);
duty.Location = "Bibliothek";
repo.Save(duty);
Assert.Equal("Bibliothek", repo.GetAll().Single().Location);
}
[Fact]
public void SupervisionDutyRepository_GetAll_SortiertNachWochentagUndPause()
{
using var db = NewInMemoryContext();
var repo = new SupervisionDutyRepository(db);
repo.Save(new SupervisionDuty { Weekday = DayOfWeek.Tuesday, AfterPeriod = 1, Location = "A" });
repo.Save(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 4, Location = "B" });
repo.Save(new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "C" });
var result = repo.GetAll();
Assert.Equal(["C", "B", "A"], result.Select(d => d.Location));
}
// ── SubstitutionEntryRepository ───────────────────────────────────────────
[Fact]
public void SubstitutionEntryRepository_GetByDate_FindetNurEintraegeDesTages()
{
using var db = NewInMemoryContext();
var repo = new SubstitutionEntryRepository(db);
repo.Save(new SubstitutionEntry { Date = new DateOnly(2026, 3, 12), Kind = SubstitutionKind.Lesson, PeriodNumber = 3, Description = "Vertretung 8a" });
repo.Save(new SubstitutionEntry { Date = new DateOnly(2026, 3, 13), Kind = SubstitutionKind.Supervision, AfterPeriod = 2, Description = "Vertretung Hr. Müller" });
var result = repo.GetByDate(new DateOnly(2026, 3, 12));
Assert.Single(result);
Assert.Equal("Vertretung 8a", result[0].Description);
}
[Fact]
public void SubstitutionEntryRepository_GetByExternalId_FindetAutomatischErzeugtenEintrag()
{
using var db = NewInMemoryContext();
var repo = new SubstitutionEntryRepository(db);
repo.Save(new SubstitutionEntry { Date = new DateOnly(2026, 3, 12), Kind = SubstitutionKind.Cancelled, PeriodNumber = 3, ExternalId = "38818-2004584-2004587" });
repo.Save(new SubstitutionEntry { Date = new DateOnly(2026, 3, 13), Kind = SubstitutionKind.Cancelled, PeriodNumber = 4 });
var result = repo.GetByExternalId("38818-2004584-2004587");
Assert.NotNull(result);
Assert.Equal(new DateOnly(2026, 3, 12), result!.Date);
Assert.Null(repo.GetByExternalId("unbekannt"));
}
// ── UntisSnapshotRepository / UntisSlotMappingRepository ─────────────────
[Fact]
public void UntisSnapshotRepository_SaveUndGetAll_RoundTrip()
{
using var db = NewInMemoryContext();
var repo = new UntisSnapshotRepository(db);
repo.Save(new UntisSnapshotEntry
{
Uid = "38926-2013812-2013815", Date = new DateOnly(2026, 8, 17),
StartTime = new TimeOnly(7, 50), EndTime = new TimeOnly(9, 20),
Summary = "SOL", Location = "Medien", Description = "10c HED",
});
var result = repo.GetAll();
Assert.Single(result);
Assert.Equal("38926-2013812-2013815", result[0].Uid);
Assert.Equal("SOL", result[0].Summary);
}
[Fact]
public void UntisSnapshotRepository_Save_AktualisiertVorhandenenEintrag()
{
using var db = NewInMemoryContext();
var repo = new UntisSnapshotRepository(db);
var entry = new UntisSnapshotEntry { Uid = "38926-2013812-2013815", Location = "Medien" };
repo.Save(entry);
entry.Location = "A1";
entry.Status = "CANCELLED";
repo.Save(entry);
var result = Assert.Single(repo.GetAll());
Assert.Equal("A1", result.Location);
Assert.Equal("CANCELLED", result.Status);
}
[Fact]
public void UntisSlotMappingRepository_SaveDeleteUndGetAll_RoundTrip()
{
using var db = NewInMemoryContext();
var repo = new UntisSlotMappingRepository(db);
var groupId = Guid.NewGuid();
var mapping = new UntisSlotMapping
{
Weekday = DayOfWeek.Monday, StartTime = new TimeOnly(7, 50),
Summary = "SOL", ClassToken = "10c", GroupId = groupId, Confirmed = true,
};
repo.Save(mapping);
Assert.Single(repo.GetAll());
repo.Delete(mapping.Id);
Assert.Empty(repo.GetAll());
}
// ── AnnualPlanEventRepository ────────────────────────────────────────────
[Fact]
public void AnnualPlanEventRepository_GetByRange_FindetAuchUeberlappendeMehrtagstermine()
{
using var db = NewInMemoryContext();
var repo = new AnnualPlanEventRepository(db);
repo.Save(new AnnualPlanEvent
{
ExternalId = "fahrt", Title = "Klassenfahrt",
StartDate = new DateOnly(2026, 6, 15), EndDate = new DateOnly(2026, 6, 19), IsAllDay = true,
});
repo.Save(new AnnualPlanEvent
{
ExternalId = "spaeter", Title = "Sommerfest",
StartDate = new DateOnly(2026, 7, 1), EndDate = new DateOnly(2026, 7, 1), IsAllDay = true,
});
var result = repo.GetByRange(new DateOnly(2026, 6, 17), new DateOnly(2026, 6, 17));
Assert.Single(result);
Assert.Equal("fahrt", result[0].ExternalId);
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 UntisStudentRosterCacheRepository_ReplaceAll_ErsetztNurDieAngegebeneKlasse()
{
using var db = NewInMemoryContext();
var repo = new UntisStudentRosterCacheRepository(db);
repo.ReplaceAll("10c", [new UntisStudentRosterCacheEntry { ClassName = "10c", DisplayName = "Alt" }]);
repo.ReplaceAll("10d", [new UntisStudentRosterCacheEntry { ClassName = "10d", DisplayName = "Andere Klasse" }]);
repo.ReplaceAll("10c", [new UntisStudentRosterCacheEntry { ClassName = "10c", DisplayName = "Neu", ExternKey = 42 }]);
var tenC = repo.GetByClass("10c");
var tenD = repo.GetByClass("10d");
Assert.Equal("Neu", Assert.Single(tenC).DisplayName);
Assert.Equal(42, tenC[0].ExternKey);
Assert.Single(tenD); // andere Klasse bleibt unberührt
}
[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]
public void WorkTaskRepository_GetByGroup_FindetNurAufgabenDieserGruppe()
{
using var db = NewInMemoryContext();
var repo = new WorkTaskRepository(db);
var groupId = Guid.NewGuid();
repo.Save(new WorkTask { Title = "Klassenaufgabe", GroupId = groupId });
repo.Save(new WorkTask { Title = "Andere Gruppe", GroupId = Guid.NewGuid() });
repo.Save(new WorkTask { Title = "Ohne Gruppe", GroupId = null });
var result = repo.GetByGroup(groupId);
Assert.Single(result);
Assert.Equal("Klassenaufgabe", result[0].Title);
}
[Fact]
public void WorkTaskRepository_SaveUndLoad_RundetPrioritaetUndChecklistItemsRoundTrip()
{
using var db = NewInMemoryContext();
var repo = new WorkTaskRepository(db);
var studentId = Guid.NewGuid();
var task = new WorkTask
{
Title = "Material einsammeln",
Priority = TaskPriority.High,
ChecklistItems = [new ChecklistItem { Label = "Anna", StudentId = studentId, IsDone = true }],
};
repo.Save(task);
var loaded = repo.GetAll().Single(t => t.Id == task.Id);
Assert.Equal(TaskPriority.High, loaded.Priority);
var item = Assert.Single(loaded.ChecklistItems);
Assert.Equal("Anna", item.Label);
Assert.Equal(studentId, item.StudentId);
Assert.True(item.IsDone);
}
}