Baustein 3: Repositories verdrahten Teil A (Kapitel 10)
OnChange-Hook auf den 19 einfachen Repositories (keine Kaskaden/Batches) ergaenzt: SeatingPlan, GroupMembership, GradingKeyTemplate, Grade, GradingScheme, ReportGrade, Unit, Lesson, WorkTask, TimeEntry, ParticipationAspect, ParticipationSection, Subject, ShorthandCode, AlternativeLessonPath, TimetableSlot, SchoolHoliday, SupervisionDuty, SubstitutionEntry. Mechanisch, ein Aufruf nach dem bestehenden Upsert/Delete. Tabellengetriebener Test statt 19 fast identischer Testdateien. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,204 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Data.Repositories;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Data.Tests;
|
||||
|
||||
// Baustein 3: prüft für jedes der ~19 "einfachen" Repositories (keine Kaskaden/Batches), dass
|
||||
// Save/Delete den OnChange-Hook mit dem korrekten EntityType auslöst. Tabellengetrieben statt
|
||||
// eine Datei pro Repository, um die immer gleiche Prüfung nicht 19x zu wiederholen.
|
||||
public sealed class ChangeHookMatrixTests
|
||||
{
|
||||
public static IEnumerable<object[]> Cases()
|
||||
{
|
||||
yield return Case("SeatingPlan", nameof(SeatingPlan), db =>
|
||||
{
|
||||
var group = new LearningGroup { Name = "8a", SchoolYear = "2025/26" };
|
||||
new GroupRepository(db).Save(group);
|
||||
var repo = new SeatingPlanRepository(db);
|
||||
var plan = new SeatingPlan { GroupId = group.Id, Name = "Standard", Rows = 2, Columns = 2 };
|
||||
repo.Save(plan);
|
||||
return (plan.Id, () => repo.Delete(plan.Id));
|
||||
});
|
||||
|
||||
yield return Case("GroupMembership", nameof(GroupMembership), db =>
|
||||
{
|
||||
var repo = new GroupMembershipRepository(db);
|
||||
var membership = new GroupMembership { StudentId = Guid.NewGuid(), GroupId = Guid.NewGuid() };
|
||||
repo.Save(membership);
|
||||
return (membership.Id, () => repo.Delete(membership.Id));
|
||||
});
|
||||
|
||||
yield return Case("GradingKeyTemplate", nameof(GradingKeyTemplate), db =>
|
||||
{
|
||||
var repo = new GradingKeyTemplateRepository(db);
|
||||
var template = new GradingKeyTemplate { Name = "Standard", GradingSystem = GradingSystem.Points0To15 };
|
||||
repo.Save(template);
|
||||
return (template.Id, () => repo.Delete(template.Id));
|
||||
});
|
||||
|
||||
yield return Case("Grade", nameof(Grade), db =>
|
||||
{
|
||||
var repo = new GradeRepository(db);
|
||||
var grade = new Grade { GroupId = Guid.NewGuid(), StudentId = Guid.NewGuid(), Value = "2" };
|
||||
repo.Save(grade);
|
||||
return (grade.Id, () => repo.Delete(grade.Id));
|
||||
});
|
||||
|
||||
yield return Case("GradingScheme", nameof(GradingScheme), db =>
|
||||
{
|
||||
var repo = new GradingSchemeRepository(db);
|
||||
var scheme = new GradingScheme { GroupId = Guid.NewGuid(), ExamsPercent = 60, ParticipationPercent = 30, OtherPercent = 10 };
|
||||
repo.Save(scheme);
|
||||
return (scheme.Id, () => repo.Delete(scheme.Id));
|
||||
});
|
||||
|
||||
yield return Case("ReportGrade", nameof(ReportGrade), db =>
|
||||
{
|
||||
var repo = new ReportGradeRepository(db);
|
||||
var grade = new ReportGrade { StudentId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Period = "1. Halbjahr" };
|
||||
repo.Save(grade);
|
||||
return (grade.Id, () => repo.Delete(grade.Id));
|
||||
});
|
||||
|
||||
yield return Case("Unit", nameof(Unit), db =>
|
||||
{
|
||||
var repo = new UnitRepository(db);
|
||||
var unit = new Unit { GroupId = Guid.NewGuid(), Title = "Einheit" };
|
||||
repo.Save(unit);
|
||||
return (unit.Id, () => repo.Delete(unit.Id));
|
||||
});
|
||||
|
||||
yield return Case("Lesson", nameof(Lesson), db =>
|
||||
{
|
||||
var repo = new LessonRepository(db);
|
||||
var lesson = new Lesson { UnitId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Date = new DateOnly(2026, 3, 12) };
|
||||
repo.Save(lesson);
|
||||
return (lesson.Id, () => repo.Delete(lesson.Id));
|
||||
});
|
||||
|
||||
yield return Case("WorkTask", nameof(WorkTask), db =>
|
||||
{
|
||||
var repo = new WorkTaskRepository(db);
|
||||
var task = new WorkTask { Title = "Klausur korrigieren", Category = TaskCategory.Correction };
|
||||
repo.Save(task);
|
||||
return (task.Id, () => repo.Delete(task.Id));
|
||||
});
|
||||
|
||||
yield return Case("TimeEntry", nameof(TimeEntry), db =>
|
||||
{
|
||||
var repo = new TimeEntryRepository(db);
|
||||
var entry = new TimeEntry { Date = new DateOnly(2026, 3, 12), DurationMinutes = 30, Category = "Vorbereitung" };
|
||||
repo.Save(entry);
|
||||
return (entry.Id, () => repo.Delete(entry.Id));
|
||||
});
|
||||
|
||||
yield return Case("ParticipationAspect", nameof(ParticipationAspect), db =>
|
||||
{
|
||||
var repo = new ParticipationAspectRepository(db);
|
||||
var aspect = new ParticipationAspect { GroupId = Guid.NewGuid(), Key = "quality", Label = "Qualität" };
|
||||
repo.Save(aspect);
|
||||
return (aspect.Id, () => repo.Delete(aspect.Id));
|
||||
});
|
||||
|
||||
yield return Case("ParticipationSection", nameof(ParticipationSection), db =>
|
||||
{
|
||||
var repo = new ParticipationSectionRepository(db);
|
||||
var section = new ParticipationSection
|
||||
{
|
||||
GroupId = Guid.NewGuid(), Label = "1. Abschnitt",
|
||||
StartDate = new DateOnly(2026, 3, 1), EndDate = new DateOnly(2026, 4, 1),
|
||||
};
|
||||
repo.Save(section);
|
||||
return (section.Id, () => repo.Delete(section.Id));
|
||||
});
|
||||
|
||||
yield return Case("Subject", nameof(Subject), db =>
|
||||
{
|
||||
var repo = new SubjectRepository(db);
|
||||
var subject = new Subject { Name = "Chemie" };
|
||||
repo.Save(subject);
|
||||
return (subject.Id, () => repo.Delete(subject.Id));
|
||||
});
|
||||
|
||||
yield return Case("ShorthandCode", nameof(ShorthandCode), db =>
|
||||
{
|
||||
var repo = new ShorthandCodeRepository(db);
|
||||
var code = new ShorthandCode { Code = "Tb", Label = "Tafelbild" };
|
||||
repo.Save(code);
|
||||
return (code.Id, () => repo.Delete(code.Id));
|
||||
});
|
||||
|
||||
yield return Case("AlternativeLessonPath", nameof(AlternativeLessonPath), db =>
|
||||
{
|
||||
var repo = new AlternativeLessonPathRepository(db);
|
||||
var path = new AlternativeLessonPath { Name = "Kurzversion" };
|
||||
repo.Save(path);
|
||||
return (path.Id, () => repo.Delete(path.Id));
|
||||
});
|
||||
|
||||
yield return Case("TimetableSlot", nameof(TimetableSlot), db =>
|
||||
{
|
||||
var repo = new TimetableSlotRepository(db);
|
||||
var slot = new TimetableSlot { GroupId = Guid.NewGuid(), Weekday = DayOfWeek.Tuesday, PeriodNumber = 3 };
|
||||
repo.Save(slot);
|
||||
return (slot.Id, () => repo.Delete(slot.Id));
|
||||
});
|
||||
|
||||
yield return Case("SchoolHoliday", nameof(SchoolHoliday), db =>
|
||||
{
|
||||
var repo = new SchoolHolidayRepository(db);
|
||||
var holiday = new SchoolHoliday { Name = "Osterferien", StartDate = new DateOnly(2026, 3, 30), EndDate = new DateOnly(2026, 4, 10) };
|
||||
repo.Save(holiday);
|
||||
return (holiday.Id, () => repo.Delete(holiday.Id));
|
||||
});
|
||||
|
||||
yield return Case("SupervisionDuty", nameof(SupervisionDuty), db =>
|
||||
{
|
||||
var repo = new SupervisionDutyRepository(db);
|
||||
var duty = new SupervisionDuty { Weekday = DayOfWeek.Monday, AfterPeriod = 2, Location = "Pausenhof" };
|
||||
repo.Save(duty);
|
||||
return (duty.Id, () => repo.Delete(duty.Id));
|
||||
});
|
||||
|
||||
yield return Case("SubstitutionEntry", nameof(SubstitutionEntry), db =>
|
||||
{
|
||||
var repo = new SubstitutionEntryRepository(db);
|
||||
var entry = new SubstitutionEntry { Date = new DateOnly(2026, 3, 12), Kind = SubstitutionKind.Lesson, PeriodNumber = 3, Description = "Vertretung 8a" };
|
||||
repo.Save(entry);
|
||||
return (entry.Id, () => repo.Delete(entry.Id));
|
||||
});
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Cases))]
|
||||
public void Save_LoestOnChangeMitKorrektemEntityTypeAus(
|
||||
string _, string entityType, Func<LiteDbContext, (Guid Id, Action Delete)> setup)
|
||||
{
|
||||
using var db = new LiteDbContext(new MemoryStream());
|
||||
var calls = new List<(string EntityType, string Operation)>();
|
||||
db.OnChange = (type, id, op, payload) => calls.Add((type, op));
|
||||
|
||||
setup(db);
|
||||
|
||||
Assert.Contains((entityType, "Save"), calls);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(Cases))]
|
||||
public void Delete_LoestOnChangeMitKorrektemEntityTypeAus(
|
||||
string _, string entityType, Func<LiteDbContext, (Guid Id, Action Delete)> setup)
|
||||
{
|
||||
using var db = new LiteDbContext(new MemoryStream());
|
||||
var (id, delete) = setup(db);
|
||||
var calls = new List<(string EntityType, string EntityId, string Operation)>();
|
||||
db.OnChange = (type, entId, op, payload) => calls.Add((type, entId, op));
|
||||
|
||||
delete();
|
||||
|
||||
Assert.Contains((entityType, id.ToString(), "Delete"), calls);
|
||||
}
|
||||
|
||||
private static object[] Case(string name, string entityType, Func<LiteDbContext, (Guid Id, Action Delete)> setup) =>
|
||||
[name, entityType, setup];
|
||||
}
|
||||
Reference in New Issue
Block a user