Files
LehrerApp/LehrerApp.Desktop.Tests/PlanningExchangeServiceTests.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

139 lines
6.0 KiB
C#

using LehrerApp.Core.Models;
using LehrerApp.Desktop.Services;
using System.Text.Json;
using Xunit;
namespace LehrerApp.Desktop.Tests;
public sealed class PlanningExchangeServiceTests
{
[Fact]
public void UnitRoundtrip_ErzeugtNeueObjekteUndErhaeltPlanungsdaten()
{
var units = new FakeUnits();
var lessons = new FakeLessons();
var shortPath = new AlternativeLessonPath { Name = "Kurzversion" };
var paths = new FakeAlternativeLessonPaths([shortPath]);
var service = new PlanningExchangeService(units, lessons, paths);
var sourceGroupId = Guid.NewGuid();
var targetGroupId = Guid.NewGuid();
var unit = new Unit
{
GroupId = sourceGroupId,
Title = "Optik",
StartDate = new DateOnly(2026, 9, 1),
EndDate = new DateOnly(2026, 10, 1),
Competencies =
[
new CompetencyReference { ItemId = Guid.NewGuid(), Code = "UF1", Description = "" },
new CompetencyReference { ItemId = Guid.NewGuid(), Code = "E4", Description = "" },
],
Status = UnitStatus.Active,
Notes = "Experimente",
};
units.Add(unit);
var sourceLesson = new Lesson
{
UnitId = unit.Id,
GroupId = sourceGroupId,
Date = new DateOnly(2026, 9, 8),
LessonNumber = 3,
Topic = "Reflexion",
StartTime = new TimeOnly(9, 50),
Status = LessonStatus.Planned,
Homework = "Aufgabe 2",
Phases =
[
new LessonPhaseStep
{
Name = "Experiment", DurationMinutes = 25, Activity = "Winkel messen",
Material = "Optikbox", Shorthand = "PA", AlternativePathId = shortPath.Id,
},
],
};
lessons.Add(sourceLesson);
var json = service.ExportUnit(unit, new PlanningExchangeContext
{
Group = "8a", Subject = "Physik", GradeLevel = 8,
});
var result = service.ImportUnit(json, targetGroupId);
Assert.DoesNotContain(unit.Id.ToString(), json, StringComparison.OrdinalIgnoreCase);
Assert.DoesNotContain(sourceLesson.Id.ToString(), json, StringComparison.OrdinalIgnoreCase);
Assert.NotEqual(unit.Id, result.Unit.Id);
Assert.Equal(targetGroupId, result.Unit.GroupId);
Assert.Equal("Optik", result.Unit.Title);
Assert.Equal(UnitStatus.Active, result.Unit.Status);
// Codes werden exportiert/importiert (Momentaufnahme-Text), aber bewusst NICHT gegen den
// Katalog der Zielinstallation aufgelöst - jede importierte Referenz bleibt ItemId=Guid.Empty
// (siehe PlanningExchangeService-Klassenkommentar: nie interne IDs übertragen).
Assert.Equal(["UF1", "E4"], result.Unit.Competencies.Select(c => c.Code));
Assert.All(result.Unit.Competencies, c => Assert.Equal(Guid.Empty, c.ItemId));
var importedLesson = Assert.Single(result.Lessons);
Assert.NotEqual(sourceLesson.Id, importedLesson.Id);
Assert.Equal(result.Unit.Id, importedLesson.UnitId);
Assert.Equal(targetGroupId, importedLesson.GroupId);
Assert.Equal(new TimeOnly(9, 50), importedLesson.StartTime);
Assert.Equal(shortPath.Id, Assert.Single(importedLesson.Phases).AlternativePathId);
}
[Fact]
public void LessonImport_AkzeptiertMarkdownCodeblockUndLegtNeueStundeAn()
{
var units = new FakeUnits();
var lessons = new FakeLessons();
var service = new PlanningExchangeService(units, lessons, new FakeAlternativeLessonPaths([]));
var source = new Lesson
{
UnitId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Date = new DateOnly(2026, 9, 8),
Topic = "Reflexion", Phases = [new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 10 }],
};
lessons.Add(source);
var json = service.ExportLesson(source, new PlanningExchangeContext());
var targetUnitId = Guid.NewGuid();
var targetGroupId = Guid.NewGuid();
var imported = service.ImportLesson($"```json\n{json}\n```", targetUnitId, targetGroupId);
Assert.NotEqual(source.Id, imported.Id);
Assert.Equal(targetUnitId, imported.UnitId);
Assert.Equal(targetGroupId, imported.GroupId);
Assert.Equal("Reflexion", imported.Topic);
Assert.Equal(10, Assert.Single(imported.Phases).DurationMinutes);
}
[Fact]
public void ImportMitFalschemSchema_SchreibtKeineDaten()
{
var units = new FakeUnits();
var lessons = new FakeLessons();
var service = new PlanningExchangeService(units, lessons, new FakeAlternativeLessonPaths([]));
const string json = """
{ "schema": "anderes.format", "version": 1, "unit": { "title": "Optik" } }
""";
var groupId = Guid.NewGuid();
var error = Assert.Throws<PlanningExchangeException>(() => service.ImportUnit(json, groupId));
Assert.Contains("Falsches Format", error.Message);
Assert.Empty(units.GetByGroup(groupId));
Assert.Empty(lessons.GetByGroupAndRange(groupId, DateOnly.MinValue, DateOnly.MaxValue));
}
[Fact]
public void ExportVerwendetDokumentiertesSchemaUndCamelCase()
{
var service = new PlanningExchangeService(new FakeUnits(), new FakeLessons(),
new FakeAlternativeLessonPaths([]));
var lesson = new Lesson { Date = new DateOnly(2026, 9, 8), Topic = "Optik" };
using var json = JsonDocument.Parse(service.ExportLesson(lesson, new PlanningExchangeContext()));
Assert.Equal(PlanningExchangeService.LessonSchema, json.RootElement.GetProperty("schema").GetString());
Assert.Equal(1, json.RootElement.GetProperty("version").GetInt32());
Assert.Equal("2026-09-08", json.RootElement.GetProperty("lesson").GetProperty("date").GetString());
Assert.Equal("planned", json.RootElement.GetProperty("lesson").GetProperty("status").GetString());
}
}