using LehrerApp.Core.Models; using LehrerApp.Data.Repositories; using Xunit; namespace LehrerApp.Data.Tests; // Baustein 4: pro öffentlichem Repository-Aufruf genau EIN Sync-Ereignis, auch wenn die // eigentliche Kaskade mehrere Collections betrifft. Batch-Methoden feuern ein Ereignis pro // betroffener Entität. public sealed class ChangeHookCascadeTests { private static LiteDbContext NewInMemoryContext() => new(new MemoryStream()); [Fact] public void GroupRepository_Delete_LoestGenauEinEreignisAusTrotzKaskade() { using var db = NewInMemoryContext(); var groupRepo = new GroupRepository(db); var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" }; groupRepo.Save(group); db.Grades.Insert(new Grade { GroupId = group.Id, StudentId = Guid.NewGuid() }); db.Exams.Insert(new Exam { GroupId = group.Id }); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); groupRepo.Delete(group.Id); var call = Assert.Single(calls); Assert.Equal((nameof(LearningGroup), "Delete"), call); } [Fact] public void ExamRepository_Delete_LoestGenauEinEreignisAusTrotzErgebnisKaskade() { using var db = NewInMemoryContext(); var repo = new ExamRepository(db); var exam = new Exam { GroupId = Guid.NewGuid() }; repo.Save(exam); db.ExamResults.Insert(new ExamResult { ExamId = exam.Id, StudentId = Guid.NewGuid() }); db.ExamResults.Insert(new ExamResult { ExamId = exam.Id, StudentId = Guid.NewGuid() }); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.Delete(exam.Id); var call = Assert.Single(calls); Assert.Equal((nameof(Exam), "Delete"), call); } [Fact] public void ParticipationSessionRepository_Delete_LoestGenauEinEreignisAusTrotzEintragKaskade() { using var db = NewInMemoryContext(); var repo = new ParticipationSessionRepository(db); var session = new ParticipationSession { GroupId = Guid.NewGuid() }; repo.Save(session); db.ParticipationEntries.Insert(new ParticipationEntry { SessionId = session.Id, StudentId = Guid.NewGuid() }); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.Delete(session.Id); var call = Assert.Single(calls); Assert.Equal((nameof(ParticipationSession), "Delete"), call); } [Fact] public void ExamResultRepository_SaveMany_LoestEinEreignisProEintragAus() { using var db = NewInMemoryContext(); var repo = new ExamResultRepository(db); var results = new List { new() { ExamId = Guid.NewGuid(), StudentId = Guid.NewGuid() }, new() { ExamId = Guid.NewGuid(), StudentId = Guid.NewGuid() }, new() { ExamId = Guid.NewGuid(), StudentId = Guid.NewGuid() }, }; var calls = new List<(string EntityType, string EntityId, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, id, op)); repo.SaveMany(results); Assert.Equal(3, calls.Count); Assert.All(calls, c => Assert.Equal((nameof(ExamResult), "Save"), (c.EntityType, c.Operation))); Assert.Equal(results.Select(r => r.Id.ToString()).OrderBy(x => x), calls.Select(c => c.EntityId).OrderBy(x => x)); } [Fact] public void ParticipationRepository_SaveMany_LoestEinEreignisProEintragAus() { using var db = NewInMemoryContext(); var repo = new ParticipationRepository(db); var sessionId = Guid.NewGuid(); var entries = new List { new() { SessionId = sessionId, StudentId = Guid.NewGuid() }, new() { SessionId = sessionId, StudentId = Guid.NewGuid() }, }; var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.SaveMany(entries); Assert.Equal(2, calls.Count); Assert.All(calls, c => Assert.Equal((nameof(ParticipationEntry), "Save"), c)); } [Fact] public void ParticipationRepository_DeleteBySession_LoestEinEreignisProEintragAus() { using var db = NewInMemoryContext(); var repo = new ParticipationRepository(db); var sessionId = Guid.NewGuid(); repo.Save(new ParticipationEntry { SessionId = sessionId, StudentId = Guid.NewGuid() }); repo.Save(new ParticipationEntry { SessionId = sessionId, StudentId = Guid.NewGuid() }); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.DeleteBySession(sessionId); Assert.Equal(2, calls.Count); Assert.All(calls, c => Assert.Equal((nameof(ParticipationEntry), "Delete"), c)); } [Fact] public void CompetencyDomainRepository_ReplaceForSubjectAndGrade_LoestDeleteFuerAlteUndSaveFuerNeueAus() { using var db = NewInMemoryContext(); var repo = new CompetencyDomainRepository(db); var subjectId = Guid.NewGuid(); repo.Save(new CompetencyDomain { SubjectId = subjectId, GradeLevel = 8, Name = "Alt" }); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.ReplaceForSubjectAndGrade(subjectId, 8, [ new CompetencyDomain { Name = "Neu 1" }, new CompetencyDomain { Name = "Neu 2" }, ]); Assert.Equal(3, calls.Count); Assert.Equal(1, calls.Count(c => c.Operation == "Delete")); Assert.Equal(2, calls.Count(c => c.Operation == "Save")); Assert.All(calls, c => Assert.Equal(nameof(CompetencyDomain), c.EntityType)); } [Fact] public void CompetencyDomainRepository_DeleteBySubjectAndGrade_LoestEinEreignisProDomaenAus() { using var db = NewInMemoryContext(); var repo = new CompetencyDomainRepository(db); var subjectId = Guid.NewGuid(); repo.Save(new CompetencyDomain { SubjectId = subjectId, GradeLevel = 8, Name = "A" }); repo.Save(new CompetencyDomain { SubjectId = subjectId, GradeLevel = 8, Name = "B" }); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.DeleteBySubjectAndGrade(subjectId, 8); Assert.Equal(2, calls.Count); Assert.All(calls, c => Assert.Equal((nameof(CompetencyDomain), "Delete"), c)); } [Fact] public void DocumentationRepository_Delete_LoestSaveMitIsDeletedAus() { using var db = NewInMemoryContext(); var repo = new DocumentationRepository(db); var doc = new Documentation { StudentId = Guid.NewGuid(), Title = "Gespräch" }; repo.Save(doc); var calls = new List<(string EntityType, string Operation, object? Payload)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op, payload)); repo.Delete(doc.Id); var call = Assert.Single(calls); Assert.Equal((nameof(Documentation), "Save"), (call.EntityType, call.Operation)); Assert.True(((Documentation)call.Payload!).IsDeleted); } [Fact] public void DocumentationRepository_HardDelete_LoestDeleteAus() { using var db = NewInMemoryContext(); var repo = new DocumentationRepository(db); var doc = new Documentation { StudentId = Guid.NewGuid(), Title = "Gespräch" }; repo.Save(doc); var calls = new List<(string EntityType, string Operation)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op)); repo.HardDelete(doc.Id); var call = Assert.Single(calls); Assert.Equal((nameof(Documentation), "Delete"), call); } [Fact] public void VorgangRepository_Delete_LoestSaveMitIsDeletedAus() { using var db = NewInMemoryContext(); var repo = new VorgangRepository(db); var vorgang = new Vorgang { Title = "Konflikt" }; repo.Save(vorgang); var calls = new List<(string EntityType, string Operation, object? Payload)>(); db.OnChange = (type, id, op, payload) => calls.Add((type, op, payload)); repo.Delete(vorgang.Id); var call = Assert.Single(calls); Assert.Equal((nameof(Vorgang), "Save"), (call.EntityType, call.Operation)); Assert.True(((Vorgang)call.Payload!).IsDeleted); } }