CI / build-and-test (push) Canceled after 0s
Ein Zwischenstand ist ein Durchgang aus Stichtag, Anlass und Schülerauswahl (ganze Gruppe, Teilgruppe oder Einzelfall, mit Schnellfilter für gefährdete Schüler). Der Zeugnisnoten-Assistent geht die gewählten Schüler im Zwischenstand-Modus durch und notiert vorläufige schriftliche, mündliche und Gesamtnote samt Notiz. Gespeichert wird das als eigenes Modell InterimGrade, getrennt von der Zeugnisnote. Die Berechnung endet am Stichtag und rechnet den laufenden Mitarbeits-Abschnitt mit ein. Zwischenstände erscheinen im Assistenten als Liste und Marker im Verlauf sowie als graue Spalte in der Notenübersicht (nicht in "Gesamt", nicht im PDF). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
222 lines
9.3 KiB
C#
222 lines
9.3 KiB
C#
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("InterimGrade", nameof(InterimGrade), db =>
|
|
{
|
|
var repo = new InterimGradeRepository(db);
|
|
var grade = new InterimGrade { StudentId = Guid.NewGuid(), GroupId = Guid.NewGuid(),
|
|
Date = new DateOnly(2025, 11, 12), Occasion = "Sprechtag", OverallValue = "3" };
|
|
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("CompetencyCatalogVersion", nameof(CompetencyCatalogVersion), db =>
|
|
{
|
|
var repo = new CompetencyCatalogVersionRepository(db);
|
|
var version = new CompetencyCatalogVersion { SubjectId = Guid.NewGuid(), Name = "Bildungsplan 2026" };
|
|
repo.Save(version);
|
|
return (version.Id, () => repo.Delete(version.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];
|
|
}
|