Files
LehrerApp/LehrerApp.Tests/CompetencyAnalysisServiceTests.cs
T
adminandClaude Sonnet 5 dc2f58c4dc feat: Kompetenzzuordnungen von Freitext-Code auf stabile CompetencyItem.Id umgestellt (TODO 16.5)
Unit/Lesson/ExamTask/ParticipationSession referenzierten Kompetenzen bisher per Freitext-Code,
case-insensitiv gegen den Katalog gematcht - fragil bei Umbenennungen und seit der
Bildungsplan-Versionierung nicht mehr eindeutig (zwei Versionen können denselben Code vergeben).
Neuer Werttyp CompetencyReference (ItemId + Code/Description-Momentaufnahme) löst das: solange die
Id noch im Katalog auflösbar ist, zählt die live Fassung (Rename wirkt sofort), sonst bleibt die
gespeicherte historische Fassung sichtbar statt spurlos zu verschwinden.

Migration (Schema-Version 6) löst bestehende Codes gegen den Katalog auf; die zuvor dreifach
duplizierte Kompetenz-Tag-UI ist jetzt in CompetencyTagGroupBuilder konsolidiert.
add_lesson_competency/remove_lesson_competency nehmen jetzt itemId statt code und validieren gegen
den Katalog (vorher ungeprüft).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-19 00:41:54 +02:00

168 lines
6.8 KiB
C#

using LehrerApp.Core.Models;
using LehrerApp.Core.Services;
using Xunit;
namespace LehrerApp.Tests;
public sealed class CompetencyAnalysisServiceTests
{
private readonly CompetencyAnalysisService _service = new();
private static readonly CompetencyItem K1 = new() { Code = "K1", Description = "Erste Kompetenz" };
private static readonly CompetencyItem K2 = new() { Code = "K2", Description = "Zweite Kompetenz", SortOrder = 1 };
private static CompetencyReference Ref(CompetencyItem item) =>
new() { ItemId = item.Id, Code = item.Code, Description = item.Description };
[Fact]
public void Analyze_UnterscheidetPlanungBehandlungUndPruefung()
{
var groupId = Guid.NewGuid();
var catalog = Catalog();
var units = new List<Unit>
{
new() { GroupId = groupId, Status = UnitStatus.Planned, Competencies = [Ref(K1)] },
new() { GroupId = groupId, Status = UnitStatus.Active, Competencies = [Ref(K1)] },
new() { GroupId = groupId, Status = UnitStatus.Completed, Competencies = [Ref(K1)] },
};
var exams = new List<Exam>
{
new() { GroupId = groupId, Status = ExamStatus.Planned,
Tasks = [new ExamTask { MaxPoints = 10, Competencies = [Ref(K1)] }] },
new() { GroupId = groupId, Status = ExamStatus.Graded,
Tasks = [new ExamTask { MaxPoints = 10, Competencies = [Ref(K1)] }] },
};
var item = Assert.Single(_service.Analyze(catalog, units, exams, []));
Assert.Equal(1, item.PlannedUnitCount);
Assert.Equal(2, item.TaughtUnitCount);
Assert.Equal(1, item.PlannedExamCount);
Assert.Equal(1, item.AssessedExamCount);
}
[Fact]
public void Analyze_BerechnetGewichteteGruppenUndSchuelerprofileAusAufgabenpunkten()
{
var firstStudent = Guid.NewGuid();
var secondStudent = Guid.NewGuid();
var absentStudent = Guid.NewGuid();
var exam = new Exam
{
Status = ExamStatus.Graded,
Tasks =
[
new ExamTask { MaxPoints = 10, Competencies = [Ref(K1)] },
new ExamTask { MaxPoints = 20, Competencies = [Ref(K1)] },
],
};
var results = new List<ExamResult>
{
new() { ExamId = exam.Id, StudentId = firstStudent, Points = [5, 10] },
new() { ExamId = exam.Id, StudentId = secondStudent, Points = [10, 20] },
new() { ExamId = exam.Id, StudentId = absentStudent, Points = [0, 0], Absent = true },
};
var item = Assert.Single(_service.Analyze(Catalog(), [], [exam], results));
Assert.NotNull(item.GroupScore);
Assert.Equal(75, item.GroupScore.Percent, 6);
Assert.Equal(2, item.GroupScore.ParticipantCount);
Assert.Equal(4, item.GroupScore.EvidenceCount);
Assert.Equal(50, item.StudentScores[firstStudent].Percent, 6);
Assert.Equal(100, item.StudentScores[secondStudent].Percent, 6);
Assert.False(item.StudentScores.ContainsKey(absentStudent));
}
[Fact]
public void Analyze_AufgabeMitMehrerenKompetenzen_WertetJedeZugeordneteKompetenzAus()
{
var studentId = Guid.NewGuid();
var exam = new Exam
{
Status = ExamStatus.Returned,
Tasks = [new ExamTask { MaxPoints = 8, Competencies = [Ref(K1), Ref(K2)] }],
};
var result = new ExamResult { ExamId = exam.Id, StudentId = studentId, Points = [6] };
var analysis = _service.Analyze(Catalog(includeSecond: true), [], [exam], [result]);
Assert.All(analysis, item => Assert.Equal(75, item.StudentScores[studentId].Percent, 6));
}
[Fact]
public void Analyze_DurchgefuehrteAberNichtKorrigierteKlausur_LiefertNochKeinProfil()
{
var studentId = Guid.NewGuid();
var exam = new Exam
{
Status = ExamStatus.Conducted,
Tasks = [new ExamTask { MaxPoints = 10, Competencies = [Ref(K1)] }],
};
var result = new ExamResult { ExamId = exam.Id, StudentId = studentId, Points = [5] };
var item = Assert.Single(_service.Analyze(Catalog(), [], [exam], [result]));
Assert.Equal(1, item.AssessedExamCount);
Assert.Null(item.GroupScore);
Assert.Empty(item.StudentScores);
}
[Fact]
public void Analyze_GeloeschteKompetenz_BleibtMitGespeicherterMomentaufnahmeSichtbar()
{
// ItemId zeigt auf kein (mehr) existierendes Katalog-Item (z.B. gelöscht) - Code/Description
// stammen aus der zum Zuordnungszeitpunkt gespeicherten Momentaufnahme.
var reference = new CompetencyReference
{
ItemId = Guid.NewGuid(), Code = "ALT1", Description = "Alte, inzwischen gelöschte Kompetenz",
};
var unit = new Unit { Status = UnitStatus.Active, Competencies = [reference] };
var analysis = _service.Analyze(Catalog(), [unit], [], []);
var unknown = Assert.Single(analysis, x => x.Code == "ALT1");
Assert.Equal("Nicht im Katalog", unknown.DomainName);
Assert.Equal("Alte, inzwischen gelöschte Kompetenz", unknown.Description);
Assert.Equal(1, unknown.TaughtUnitCount);
}
[Fact]
public void Analyze_NieAufgeloesteReferenz_OhneBeschreibung_ZeigtFallbacktext()
{
// ItemId == Guid.Empty simuliert eine nie aufgelöste Referenz (z.B. aus einem
// plattformübergreifenden Import über PlanningExchangeService) ohne gespeicherte Beschreibung.
var reference = new CompetencyReference { ItemId = Guid.Empty, Code = "IMPORT1", Description = "" };
var unit = new Unit { Status = UnitStatus.Active, Competencies = [reference] };
var analysis = _service.Analyze(Catalog(), [unit], [], []);
var unknown = Assert.Single(analysis, x => x.Code == "IMPORT1");
Assert.Contains("nicht (mehr) im aktuellen Katalog", unknown.Description);
}
[Fact]
public void Analyze_UmbenannteKompetenz_ZeigtLiveFassungStattMomentaufnahme()
{
// Referenz trägt eine veraltete Momentaufnahme (Description von vor dem Rename) - solange
// ItemId noch im Katalog auflösbar ist, muss die LIVE-Fassung gewinnen.
var reference = new CompetencyReference { ItemId = K1.Id, Code = "K1", Description = "Veraltete Beschreibung" };
var unit = new Unit { Status = UnitStatus.Active, Competencies = [reference] };
var item = Assert.Single(_service.Analyze(Catalog(), [unit], [], []));
Assert.Equal("Erste Kompetenz", item.Description);
Assert.Equal("Bereich", item.DomainName);
}
private static List<CompetencyDomain> Catalog(bool includeSecond = false) =>
[
new CompetencyDomain
{
Name = "Bereich",
Code = "B",
Items = includeSecond ? [K1, K2] : [K1],
},
];
}