Ersetzt den ursprünglichen KI-Websuche-Ansatz (GESTIS scheiterte sowohl über Websuche als auch über direkten API-Zugriff an fehlenden schulspezifischen Tätigkeitsbeschränkungen) durch einen reinen lokalen Datenbank-Lookup gegen die offizielle RiSU-Stoffliste (1764 Stoffe inkl. Tätigkeitsbeschränkungs-Codes, Legende zur Abfragezeit übersetzt statt in die Datenbank gebacken). Keine KI-Kosten, keine Drittanbieter-Abhängigkeit mehr für diese Funktion. HazardSubstance.AgeRestriction zu ActivityRestriction umbenannt, da die Tätigkeitsbeschränkung mehr abdeckt als reine Altersgrenzen. Details und verworfene Zwischenstände in TODO.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
187 lines
8.1 KiB
C#
187 lines
8.1 KiB
C#
using LehrerApp.Desktop.Services;
|
||
using Xunit;
|
||
|
||
namespace LehrerApp.Desktop.Tests;
|
||
|
||
public sealed class PdfExportServiceTests
|
||
{
|
||
private static readonly PdfExportService Service = new();
|
||
|
||
private static void AssertIsPdf(byte[] bytes)
|
||
{
|
||
Assert.True(bytes.Length > 500, "PDF ist verdächtig klein.");
|
||
Assert.Equal("%PDF"u8.ToArray(), bytes[..4]);
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildSeatingPlanPdf_VollstaendigerPlan_LiefertGueltigesPdf()
|
||
{
|
||
var data = new SeatingPlanPrintData("Standard", "Raum B204 · 2 × 3 Plätze",
|
||
Columns: 3, IsBoardAtBottom: false, ColumnGapWidths: [0, 90],
|
||
Seats:
|
||
[
|
||
new SeatPrintCell(0, 0, "Beispiel, Anna", false),
|
||
new SeatPrintCell(0, 1, null, false),
|
||
new SeatPrintCell(0, 2, "Muster, Ben", false),
|
||
new SeatPrintCell(1, 0, null, false),
|
||
new SeatPrintCell(1, 1, "Probe, Cem", false),
|
||
new SeatPrintCell(1, 2, null, false),
|
||
]);
|
||
|
||
AssertIsPdf(Service.BuildSeatingPlanPdf(data));
|
||
}
|
||
|
||
/// Ausgeblendete Plätze (kein Tisch im Raum, siehe SeatingPlan.HiddenSeats) müssen als leere
|
||
/// Rasterposition mitgedruckt werden, ohne die Erzeugung zu stören.
|
||
[Fact]
|
||
public void BuildSeatingPlanPdf_MitAusgeblendetenPlaetzenUndTafelUnten_LiefertGueltigesPdf()
|
||
{
|
||
var data = new SeatingPlanPrintData("Physikraum", "",
|
||
Columns: 2, IsBoardAtBottom: true, ColumnGapWidths: [],
|
||
Seats:
|
||
[
|
||
new SeatPrintCell(0, 0, "Beispiel, Anna", false),
|
||
new SeatPrintCell(0, 1, null, true),
|
||
new SeatPrintCell(1, 0, null, true),
|
||
new SeatPrintCell(1, 1, null, false),
|
||
]);
|
||
|
||
AssertIsPdf(Service.BuildSeatingPlanPdf(data));
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildGradeListPdf_MatrixMitMehrerenSpalten_LiefertGueltigesPdf()
|
||
{
|
||
var data = new GradeListPrintData("8a Chemie", "2026/27", "1. Halbjahr",
|
||
ColumnHeaders: ["12.09. Klausur 1", "Mündlich 20.09.", "Hausaufgaben 01.10."],
|
||
Rows:
|
||
[
|
||
new GradeListPrintRow("Beispiel, Anna", ["2", "1", ""], "1.67"),
|
||
new GradeListPrintRow("Muster, Ben", ["abwesend", "3", "2"], "2.50"),
|
||
new GradeListPrintRow("Probe, Cem", ["", "", ""], "–"),
|
||
]);
|
||
|
||
AssertIsPdf(Service.BuildGradeListPdf(data));
|
||
}
|
||
|
||
/// Ein Kurs kann übers Jahr viele Notenspalten ansammeln - die Tabelle muss auch dann noch
|
||
/// erzeugbar sein (QuestPDF staucht die Spalten, statt zu werfen).
|
||
[Fact]
|
||
public void BuildGradeListPdf_VieleSpalten_LiefertGueltigesPdf()
|
||
{
|
||
var headers = Enumerable.Range(1, 20).Select(i => $"Note {i}").ToList();
|
||
var rows = Enumerable.Range(1, 30).Select(i => new GradeListPrintRow(
|
||
$"Schüler {i}", [.. Enumerable.Repeat("2", 20)], "2.00")).ToList();
|
||
|
||
AssertIsPdf(Service.BuildGradeListPdf(new GradeListPrintData("LK Chemie", "2026/27",
|
||
"Gesamtes Schuljahr", headers, rows)));
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildExamStatisticsPdf_MitVerteilungUndAufgabenanalyse_LiefertGueltigesPdf()
|
||
{
|
||
var data = new ExamStatisticsPrintData("Klausur 1 - Säuren und Basen", "12.09.2026", "E",
|
||
Stats:
|
||
[
|
||
new LabelValue("Bewertet", "24"),
|
||
new LabelValue("Abwesend", "2"),
|
||
new LabelValue("Durchschnitt", "2.71"),
|
||
new LabelValue("Median", "3"),
|
||
new LabelValue("Anteil nicht ausreichend (Note 5/6)", "8.3 %"),
|
||
],
|
||
Distribution:
|
||
[
|
||
new GradeDistributionPrintRow("1", 3, 12.5),
|
||
new GradeDistributionPrintRow("2", 8, 33.3),
|
||
new GradeDistributionPrintRow("3", 7, 29.2),
|
||
new GradeDistributionPrintRow("4", 4, 16.7),
|
||
new GradeDistributionPrintRow("5", 2, 8.3),
|
||
new GradeDistributionPrintRow("6", 0, 0),
|
||
],
|
||
TaskAnalysis:
|
||
[
|
||
new TaskAnalysisPrintRow("1. Titration", 78.4, false),
|
||
new TaskAnalysisPrintRow("2. pH-Berechnung", 42.1, true),
|
||
]);
|
||
|
||
AssertIsPdf(Service.BuildExamStatisticsPdf(data));
|
||
}
|
||
|
||
/// Auch ohne Aufgabenstruktur (Klausur ohne Teilaufgaben) und ohne Niveau muss der
|
||
/// Notenspiegel druckbar sein - der Abschnitt Aufgabenanalyse entfällt dann einfach.
|
||
[Fact]
|
||
public void BuildExamStatisticsPdf_OhneAufgabenUndNiveau_LiefertGueltigesPdf()
|
||
{
|
||
var data = new ExamStatisticsPrintData("Test", "01.10.2026", "",
|
||
[new LabelValue("Bewertet", "0")], [], []);
|
||
|
||
AssertIsPdf(Service.BuildExamStatisticsPdf(data));
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildCompetencyReportPdf_LiefertGueltigesPdf()
|
||
{
|
||
var data = new CompetencyReportPrintData("8a Chemie", "Beispiel, Anna",
|
||
"3 von 5 behandelt · 2 geprüft · 2 mit Ergebnisdaten", "Wiederholungsbedarf unter 60 %",
|
||
[
|
||
new CompetencyReportPrintRow("Stoff-Teilchen (ST)", "ST1",
|
||
"Aggregatzustände auf Teilchenebene erklären", "2× behandelt", "1× geprüft",
|
||
"72.5 % · 24 Schüler · 3 Aufgabenwerte", "85 % · 3 Aufgabenwerte", "Stand solide"),
|
||
new CompetencyReportPrintRow("Chemische Reaktion (CR)", "CR2",
|
||
"Reaktionsgleichungen aufstellen", "1× behandelt · 1× geplant", "1× geprüft",
|
||
"48.3 % · 24 Schüler · 2 Aufgabenwerte", "40 % · 2 Aufgabenwerte", "Wiederholungsbedarf"),
|
||
new CompetencyReportPrintRow("Energie (EN)", "–", "Energiediagramme deuten",
|
||
"–", "1× geplant", "–", "–", "Noch keine Bewertung"),
|
||
]);
|
||
|
||
AssertIsPdf(Service.BuildCompetencyReportPdf(data));
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildStudentDocumentationPdf_MitVertraulichenUndEntwurfsEintraegen_LiefertGueltigesPdf()
|
||
{
|
||
var data = new StudentDocumentationPrintData("Beispiel, Anna",
|
||
[
|
||
new DocumentationPrintEntry("12.09.2026", "Beobachtung", "Unterricht gestört",
|
||
"Wiederholt dazwischengerufen, nach Ermahnung gebessert.",
|
||
["Kritisch"], IsConfidential: false, IsDraft: false),
|
||
new DocumentationPrintEntry("20.09.2026", "Elterngespräch", "Telefonat Mutter",
|
||
"Absprache: wöchentliche Rückmeldung im Hausaufgabenheft.",
|
||
[], IsConfidential: true, IsDraft: false),
|
||
new DocumentationPrintEntry("01.10.2026", "Beobachtung", "Schnellnotiz",
|
||
"", ["Nacharbeiten"], IsConfidential: false, IsDraft: true),
|
||
]);
|
||
|
||
AssertIsPdf(Service.BuildStudentDocumentationPdf(data));
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildHazardAssessmentPdf_MitStoffenUndKiHinweis_LiefertGueltigesPdf()
|
||
{
|
||
var data = new HazardAssessmentPrintData(
|
||
"Natrium in Wasser", "10c", "12.09.2026", "Lehrerversuch",
|
||
"Ein kleines Stück Natrium wird in eine Schale mit Wasser gegeben.",
|
||
[
|
||
new HazardSubstancePrintRow("Natrium", "7440-23-5", "ca. 0,5 g", "GHS02, GHS05", "Gefahr",
|
||
"H260 - Reagiert mit Wasser unter Bildung entzündbarer Gase", "P223, P231+P232, P370+P378",
|
||
"Abgabe an unter 18-Jährige verboten"),
|
||
],
|
||
["Wasserstoffbildung, Verpuffungsgefahr", "Verätzungsgefahr durch Natronlauge"],
|
||
["Schutzbrille", "Sicherheitsabstand einhalten", "Abzug/Freiluftversuch"],
|
||
"Betroffene Stellen mit viel Wasser spülen, Arzt hinzuziehen.",
|
||
"Reste unter Aufsicht vollständig abreagieren lassen.",
|
||
"", IsAiAssisted: true);
|
||
|
||
AssertIsPdf(Service.BuildHazardAssessmentPdf(data));
|
||
}
|
||
|
||
[Fact]
|
||
public void BuildHazardAssessmentPdf_OhneStoffeUndOhneKiHinweis_LiefertGueltigesPdf()
|
||
{
|
||
var data = new HazardAssessmentPrintData(
|
||
"Papierchromatographie", "8b", "", "Schülerversuch", "", [], [], [], "", "", "", IsAiAssisted: false);
|
||
|
||
AssertIsPdf(Service.BuildHazardAssessmentPdf(data));
|
||
}
|
||
}
|