212 lines
8.5 KiB
C#
212 lines
8.5 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_MitarbeitMitAutomatischerSchaetzung_FuelltKompetenzprofil()
|
|
{
|
|
var studentId = Guid.NewGuid();
|
|
var session = new ParticipationSession { Competencies = [Ref(K1)] };
|
|
var entry = new ParticipationEntry
|
|
{
|
|
SessionId = session.Id,
|
|
StudentId = studentId,
|
|
Ratings =
|
|
[
|
|
new AspectRating { Key = "quality", Value = 1 },
|
|
new AspectRating { Key = "quantity", Value = 2 },
|
|
],
|
|
};
|
|
|
|
var item = Assert.Single(_service.Analyze(Catalog(), [], [], [], [session], [entry],
|
|
[.. DefaultParticipationAspects.All]));
|
|
|
|
Assert.Equal(1, item.ParticipationSessionCount);
|
|
Assert.Equal(100, item.GroupScore!.Percent, 6); // Mittel 1,5 wird wie in der UI auf +2 gerundet.
|
|
Assert.Equal(1, item.GroupScore.ParticipationEvidenceCount);
|
|
Assert.Equal(100, item.StudentScores[studentId].Percent, 6);
|
|
}
|
|
|
|
[Fact]
|
|
public void Analyze_ManuelleKompetenzbewertung_UeberschreibtAutomatischeSchaetzung()
|
|
{
|
|
var studentId = Guid.NewGuid();
|
|
var session = new ParticipationSession { Competencies = [Ref(K1)] };
|
|
var entry = new ParticipationEntry
|
|
{
|
|
SessionId = session.Id,
|
|
StudentId = studentId,
|
|
Ratings = [new AspectRating { Key = "quality", Value = 2 }],
|
|
CompetencyRatings = [new CompetencyRating { ItemId = K1.Id, Value = -1 }],
|
|
};
|
|
|
|
var item = Assert.Single(_service.Analyze(Catalog(), [], [], [], [session], [entry],
|
|
[.. DefaultParticipationAspects.All]));
|
|
|
|
Assert.Equal(25, item.StudentScores[studentId].Percent, 6);
|
|
}
|
|
|
|
[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],
|
|
},
|
|
];
|
|
}
|