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(() => 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(() => 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(() => new GroupMembershipRepository(db).Save(membership)); Assert.Throws(() => new ExamRepository(db).Save(exam)); Assert.Throws(() => new GradeRepository(db).Save( new Grade { GroupId = group.Id, StudentId = Guid.NewGuid(), Value = "2" })); Assert.Throws(() => new UnitRepository(db).Save( new Unit { GroupId = group.Id, Title = "Einheit" })); Assert.Throws(() => 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(() => repo.Save(archived)); archived.IsActive = true; Assert.Throws(() => 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(() => new SeatingPlanRepository(db).Save(plan)); } [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(() => 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(() => 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(() => 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(() => 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(() => repo.Save(new ParticipationAspect { GroupId = groupId, Key = " ", Label = "Experiment" })); Assert.Throws(() => 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(() => 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(() => 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(() => 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)); } // ── 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); } // ── 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(() => 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(() => 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(() => 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(() => 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); } }