feat: Sitzungsfreigabe für MCP-Bestätigungen + LessonStatus.Cancelled
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>
This commit is contained in:
@@ -610,11 +610,15 @@ public class FakeMcpConfirmation : IMcpConfirmationService
|
||||
public string? LastMessage { get; private set; }
|
||||
public int CallCount { get; private set; }
|
||||
|
||||
public Task<bool> ConfirmAsync(string title, string message, CancellationToken ct)
|
||||
public string? LastOperationKey { get; private set; }
|
||||
|
||||
public Task<bool> ConfirmAsync(string title, string message, CancellationToken ct,
|
||||
[System.Runtime.CompilerServices.CallerMemberName] string operationKey = "")
|
||||
{
|
||||
CallCount++;
|
||||
LastTitle = title;
|
||||
LastMessage = message;
|
||||
LastOperationKey = operationKey;
|
||||
return Task.FromResult(Response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,39 @@ public sealed class LessonSchedulingServiceTests
|
||||
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()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using LehrerApp.Core.Models;
|
||||
using LehrerApp.Desktop.ViewModels.Groups;
|
||||
using Xunit;
|
||||
|
||||
namespace LehrerApp.Desktop.Tests;
|
||||
|
||||
public sealed class LessonStatusDisplayTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(LessonStatus.Draft, "Entwurf")]
|
||||
[InlineData(LessonStatus.Ready, "Bereit")]
|
||||
[InlineData(LessonStatus.Conducted, "Durchgeführt")]
|
||||
[InlineData(LessonStatus.Cancelled, "Ausgefallen")]
|
||||
[InlineData(LessonStatus.Planned, "Geplant")]
|
||||
public void ToName_FromName_RoundTrip(LessonStatus status, string expectedName)
|
||||
{
|
||||
Assert.Equal(expectedName, LessonStatusDisplay.ToName(status));
|
||||
Assert.Equal(status, LessonStatusDisplay.FromName(expectedName));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnitSummary_AusgefalleneStundenZaehlenWederAlsGehaltenNochAlsPensum()
|
||||
{
|
||||
var unit = new Unit { Title = "Optik" };
|
||||
var lessons = new List<Lesson>
|
||||
{
|
||||
new() { Status = LessonStatus.Conducted },
|
||||
new() { Status = LessonStatus.Cancelled },
|
||||
new() { Status = LessonStatus.Ready },
|
||||
};
|
||||
|
||||
var summary = new UnitSummary(unit, lessons);
|
||||
|
||||
Assert.Equal(2, summary.TotalCount);
|
||||
Assert.Equal(1, summary.ConductedCount);
|
||||
Assert.Equal(0.5, summary.ProgressFraction);
|
||||
}
|
||||
}
|
||||
@@ -340,6 +340,25 @@ public sealed class McpToolsTests
|
||||
Assert.Equal(result.Id, phase.Id);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AddLessonPhase_UebergibtEigenenMethodennamenAlsOperationKey()
|
||||
{
|
||||
// Belegt, dass IMcpConfirmationService.ConfirmAsync den [CallerMemberName]-Mechanismus
|
||||
// tatsächlich nutzt (Grundlage für die Sitzungsfreigabe "diese Aktion nicht mehr
|
||||
// nachfragen" in AvaloniaMcpConfirmationService) - ohne echtes UI testbar, weil der Name
|
||||
// vom Compiler an der Aufrufstelle in AddLessonPhase eingesetzt wird, unabhängig von der
|
||||
// konkreten IMcpConfirmationService-Implementierung.
|
||||
var lesson = new Lesson { Topic = "Brechung" };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
var confirmation = new FakeMcpConfirmation();
|
||||
var tool = BuildLessonPlanTools(lessons: lessons, confirmation: confirmation);
|
||||
|
||||
await tool.AddLessonPhase(lesson.Id, "Einstieg", 10);
|
||||
|
||||
Assert.Equal(nameof(LessonPlanTools.AddLessonPhase), confirmation.LastOperationKey);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task UpdateLessonPhase_AendertNurAngegebeneFelder()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user