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>
139 lines
6.0 KiB
C#
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());
|
|
}
|
|
}
|