Sitzungsfreigabe: McpConfirmDialog bietet zwei Checkboxen ("diese Art
von Aktion" / "alle KI-Aktionen für den Rest der Sitzung nicht mehr
nachfragen"), nur beim Bestätigen wirksam. IMcpConfirmationService.
ConfirmAsync bekommt einen per [CallerMemberName] automatisch
befüllten operationKey - kein bestehender Aufruf musste geändert
werden. Freigaben leben als In-Memory-Bookkeeping auf der
Confirmation-Service-Instanz, gelten bis App-Ende, werden aber trotzdem
geloggt, damit stillschweigende Bestätigungen nicht spurlos bleiben.
Grund: 17 identische Bestätigungen in Folge für eine neu generierte
Unterrichtseinheit sind unzumutbar.
LessonStatus.Cancelled ("Ausgefallen") als Alternative zu
delete_lesson für Stunden, die nur ausgefallen sind (Exkursion,
Feiertag), aber als Ereignis dokumentiert bleiben sollen. Als
Cancelled=4 angehängt (bestehende LiteDB-Werte 0/1 sind fix). Konsistent
wie "bereits durchgeführt" behandelt an allen Stellen, die bisher nur
auf Conducted prüften: Terminverschiebung, Fortschrittsanzeige,
Hausaufgabe-kontrollieren-Erinnerung, "nächste Stunde"-Vorschlag.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
124 lines
5.1 KiB
C#
124 lines
5.1 KiB
C#
using LehrerApp.Core.Models;
|
||
using LehrerApp.Core.Services;
|
||
using LehrerApp.Desktop.ViewModels.Planning;
|
||
using Xunit;
|
||
|
||
namespace LehrerApp.Desktop.Tests;
|
||
|
||
public sealed class LessonSchedulingServiceTests
|
||
{
|
||
[Fact]
|
||
public void UnitPicker_SchlaegtLaufendeEinheitVor_UndSpeichertNeuanlageNochNicht()
|
||
{
|
||
var groupId = Guid.NewGuid();
|
||
var planned = new Unit { GroupId = groupId, Title = "Später", Status = UnitStatus.Planned };
|
||
var active = new Unit { GroupId = groupId, Title = "Aktuell", Status = UnitStatus.Active };
|
||
var repo = new FakeUnits();
|
||
repo.Add(planned); repo.Add(active);
|
||
var vm = new TimetableUnitPickerViewModel(repo, groupId, "10c", new(2026, 9, 2), 3);
|
||
|
||
Assert.Equal(active.Id, vm.SelectedUnit?.Model.Id);
|
||
vm.NewUnitTitle = "Neue Reihe";
|
||
vm.SaveCommand.Execute(null);
|
||
|
||
Assert.True(vm.ResultIsNew);
|
||
Assert.Equal("Neue Reihe", vm.Result?.Title);
|
||
Assert.Null(repo.GetById(vm.Result!.Id));
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_AendertDatumUndStunde_UndRuecktNurNichtDurchgefuehrteFolgestundenNach()
|
||
{
|
||
var unitId = Guid.NewGuid();
|
||
var groupId = Guid.NewGuid();
|
||
var moved = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 1), LessonNumber = 2 };
|
||
var draftFollowing = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 3), Status = LessonStatus.Draft };
|
||
var conducted = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 4), Status = LessonStatus.Conducted };
|
||
var repo = new FakeLessons();
|
||
repo.Add(moved); repo.Add(draftFollowing); repo.Add(conducted);
|
||
|
||
new LessonSchedulingService(repo).Move(moved, new(2026, 9, 8), 5, shiftFollowing: true);
|
||
|
||
Assert.Equal(new DateOnly(2026, 9, 8), moved.Date);
|
||
Assert.Equal(5, moved.LessonNumber);
|
||
Assert.Equal(new DateOnly(2026, 9, 10), draftFollowing.Date);
|
||
Assert.Equal(new DateOnly(2026, 9, 4), conducted.Date);
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_RuecktAuchAusgefalleneFolgestundenNichtNach()
|
||
{
|
||
var unitId = Guid.NewGuid();
|
||
var groupId = Guid.NewGuid();
|
||
var moved = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 1) };
|
||
var cancelledFollowing = new Lesson { UnitId = unitId, GroupId = groupId, Date = new(2026, 9, 4), Status = LessonStatus.Cancelled };
|
||
var repo = new FakeLessons();
|
||
repo.Add(moved); repo.Add(cancelledFollowing);
|
||
|
||
new LessonSchedulingService(repo).Move(moved, new(2026, 9, 8), null, shiftFollowing: true);
|
||
|
||
Assert.Equal(new DateOnly(2026, 9, 4), cancelledFollowing.Date);
|
||
}
|
||
|
||
[Fact]
|
||
public void SplitAndMoveSecondPart_AusgefalleneQuellstundeSetztFortsetzungAufEntwurfZurueck()
|
||
{
|
||
var source = new Lesson
|
||
{
|
||
UnitId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Date = new(2026, 9, 1),
|
||
Status = LessonStatus.Cancelled,
|
||
Phases = [new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 60 }],
|
||
};
|
||
var repo = new FakeLessons();
|
||
repo.Add(source);
|
||
|
||
var continuation = new LessonSchedulingService(repo).SplitAndMoveSecondPart(source, 30,
|
||
new(2026, 9, 3), 1, null);
|
||
|
||
Assert.Equal(LessonStatus.Draft, continuation.Status);
|
||
}
|
||
|
||
[Fact]
|
||
public void SplitAndMoveSecondPart_TeiltAuchEineUeberDieGrenzeLaufendePhase()
|
||
{
|
||
var source = new Lesson
|
||
{
|
||
UnitId = Guid.NewGuid(), GroupId = Guid.NewGuid(), Date = new(2026, 9, 1),
|
||
LessonNumber = 3, Topic = "Fotosynthese", Homework = "Aufgabe 2",
|
||
Phases =
|
||
[
|
||
new LessonPhaseStep { Name = "Einstieg", DurationMinutes = 30 },
|
||
new LessonPhaseStep { Name = "Experiment", DurationMinutes = 30 },
|
||
new LessonPhaseStep { Name = "Sicherung", DurationMinutes = 20 },
|
||
],
|
||
};
|
||
var repo = new FakeLessons();
|
||
repo.Add(source);
|
||
|
||
var continuation = new LessonSchedulingService(repo).SplitAndMoveSecondPart(source, 45,
|
||
new(2026, 9, 3), 6, new TimeOnly(12, 15));
|
||
|
||
Assert.Equal([30, 15], source.Phases.Select(p => p.DurationMinutes));
|
||
Assert.Equal([15, 20], continuation.Phases.Select(p => p.DurationMinutes));
|
||
Assert.Equal("Fotosynthese – Fortsetzung", continuation.Topic);
|
||
Assert.Null(source.Homework);
|
||
Assert.Equal("Aufgabe 2", continuation.Homework);
|
||
Assert.Equal(6, continuation.LessonNumber);
|
||
}
|
||
|
||
[Fact]
|
||
public void Move_LehntBelegtenZielterminAb()
|
||
{
|
||
var groupId = Guid.NewGuid();
|
||
var source = new Lesson { GroupId = groupId, Date = new(2026, 9, 1), LessonNumber = 1 };
|
||
var occupied = new Lesson { GroupId = groupId, Date = new(2026, 9, 2), LessonNumber = 4 };
|
||
var repo = new FakeLessons();
|
||
repo.Add(source); repo.Add(occupied);
|
||
|
||
var error = Assert.Throws<InvalidOperationException>(() =>
|
||
new LessonSchedulingService(repo).Move(source, occupied.Date, 4, false));
|
||
|
||
Assert.Contains("bereits eine Planung", error.Message);
|
||
}
|
||
}
|