feat: prominenterer Bestätigungsdialog + move_lesson/delete_lesson (Nutzer-Feedback)
Nach dem ersten Live-Test mit echtem Bridge-Prozess: der bisherige ConfirmDialog fiel zu wenig auf, wenn LehrerApp im Hintergrund lief (Normalfall, da der Anstoß vom KI-Client in einem anderen Fenster kommt). Neuer, eigenständiger McpConfirmDialog statt Änderung am geteilten ConfirmDialog (hätte alle anderen Aufrufer mitbetroffen): breiter, auffälliger Kopfbereich, Topmost. AvaloniaMcpConfirmationService holt das Hauptfenster zusätzlich aus einer möglichen Minimierung und aktiviert es vor dem Anzeigen. move_lesson kapselt die bereits vorhandene LessonSchedulingService.Move (shiftFollowingLessons öffnet eine Lücke für eine neue Stunde, indem spätere Stunden derselben Einheit mitverschoben werden) - keine neue Terminlogik, nur Wiederverwendung. delete_lesson ist eine bewusste, gezielte Ausnahme von "v1 ohne Lösch-Tools" auf expliziten Nutzerwunsch: eigene AllowedDestructiveWriteTools-Liste, Destructive=true-Annotation, Bestätigungstext betont ausdrücklich die fehlende Papierkorb-Deckung für Lessons. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,12 +29,18 @@ public sealed class McpToolsTests
|
||||
new[]
|
||||
{
|
||||
"add_lesson_attachment", "add_lesson_phase", "create_grade_entry", "create_lesson",
|
||||
"create_time_entry", "create_unit", "remove_lesson_phase", "update_lesson",
|
||||
"update_lesson_phase", "update_student_group_assignment", "update_unit",
|
||||
"create_time_entry", "create_unit", "move_lesson", "remove_lesson_phase",
|
||||
"update_lesson", "update_lesson_phase", "update_student_group_assignment", "update_unit",
|
||||
},
|
||||
McpToolScope.AllowedWriteTools.OrderBy(n => n, StringComparer.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowedDestructiveWriteTools_EnthaeltNurDeleteLesson()
|
||||
{
|
||||
Assert.Equal(["delete_lesson"], McpToolScope.AllowedDestructiveWriteTools);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AllowedTools_EnthaeltKeineDokumentationstypen()
|
||||
{
|
||||
@@ -463,6 +469,110 @@ public sealed class McpToolsTests
|
||||
Assert.Equal(0, confirmation.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MoveLesson_NutzerBestaetigt_VerschiebtDatum()
|
||||
{
|
||||
var lesson = new Lesson { Topic = "Brechung", Date = new DateOnly(2026, 1, 5) };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
var tool = BuildLessonPlanTools(lessons: lessons);
|
||||
|
||||
var result = await tool.MoveLesson(lesson.Id, new DateOnly(2026, 1, 12));
|
||||
|
||||
Assert.True(result.Applied);
|
||||
Assert.Equal(new DateOnly(2026, 1, 12), lessons.GetById(lesson.Id)!.Date);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MoveLesson_ShiftFollowing_VerschiebtSpaetereStundenDerselbenEinheitMit()
|
||||
{
|
||||
var unitId = Guid.NewGuid();
|
||||
var moved = new Lesson { UnitId = unitId, Topic = "Brechung", Date = new DateOnly(2026, 1, 5) };
|
||||
var later = new Lesson { UnitId = unitId, Topic = "Reflexion", Date = new DateOnly(2026, 1, 7) };
|
||||
var earlier = new Lesson { UnitId = unitId, Topic = "Einstieg", Date = new DateOnly(2026, 1, 1) };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(moved); lessons.Add(later); lessons.Add(earlier);
|
||||
var tool = BuildLessonPlanTools(lessons: lessons);
|
||||
|
||||
// moved: 5.1. -> 12.1. (Delta +7 Tage), later (7.1., danach) muss mitwandern, earlier (1.1., davor) nicht.
|
||||
var result = await tool.MoveLesson(moved.Id, new DateOnly(2026, 1, 12), shiftFollowingLessons: true);
|
||||
|
||||
Assert.True(result.Applied);
|
||||
Assert.Equal(new DateOnly(2026, 1, 14), lessons.GetById(later.Id)!.Date);
|
||||
Assert.Equal(new DateOnly(2026, 1, 1), lessons.GetById(earlier.Id)!.Date);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MoveLesson_ZielStundeBelegt_LiefertFehlerNachBestaetigung()
|
||||
{
|
||||
var groupId = Guid.NewGuid();
|
||||
var moved = new Lesson { GroupId = groupId, Topic = "Brechung", Date = new DateOnly(2026, 1, 5), LessonNumber = 1 };
|
||||
var occupying = new Lesson { GroupId = groupId, Topic = "Anderer Kurs", Date = new DateOnly(2026, 1, 12), LessonNumber = 1 };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(moved); lessons.Add(occupying);
|
||||
var confirmation = new FakeMcpConfirmation();
|
||||
var tool = BuildLessonPlanTools(lessons: lessons, confirmation: confirmation);
|
||||
|
||||
var result = await tool.MoveLesson(moved.Id, new DateOnly(2026, 1, 12), newPeriod: 1);
|
||||
|
||||
Assert.False(result.Applied);
|
||||
Assert.Equal(1, confirmation.CallCount); // Bestätigung lief bereits, Konflikt zeigt sich erst beim Speichern.
|
||||
Assert.Equal(new DateOnly(2026, 1, 5), lessons.GetById(moved.Id)!.Date); // unverändert
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task MoveLesson_UnbekannteStunde_LiefertFehlerOhneNachfrage()
|
||||
{
|
||||
var confirmation = new FakeMcpConfirmation();
|
||||
var tool = BuildLessonPlanTools(confirmation: confirmation);
|
||||
|
||||
var result = await tool.MoveLesson(Guid.NewGuid(), new DateOnly(2026, 1, 12));
|
||||
|
||||
Assert.False(result.Applied);
|
||||
Assert.Equal(0, confirmation.CallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteLesson_NutzerBestaetigt_LoeschtEndgueltig()
|
||||
{
|
||||
var lesson = new Lesson { Topic = "Brechung" };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
var tool = BuildLessonPlanTools(lessons: lessons);
|
||||
|
||||
var result = await tool.DeleteLesson(lesson.Id);
|
||||
|
||||
Assert.True(result.Applied);
|
||||
Assert.Null(lessons.GetById(lesson.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteLesson_NutzerLehntAb_BleibtErhalten()
|
||||
{
|
||||
var lesson = new Lesson { Topic = "Brechung" };
|
||||
var lessons = new FakeLessons();
|
||||
lessons.Add(lesson);
|
||||
var confirmation = new FakeMcpConfirmation { Response = false };
|
||||
var tool = BuildLessonPlanTools(lessons: lessons, confirmation: confirmation);
|
||||
|
||||
var result = await tool.DeleteLesson(lesson.Id);
|
||||
|
||||
Assert.False(result.Applied);
|
||||
Assert.NotNull(lessons.GetById(lesson.Id));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task DeleteLesson_UnbekannteStunde_LiefertFehlerOhneNachfrage()
|
||||
{
|
||||
var confirmation = new FakeMcpConfirmation();
|
||||
var tool = BuildLessonPlanTools(confirmation: confirmation);
|
||||
|
||||
var result = await tool.DeleteLesson(Guid.NewGuid());
|
||||
|
||||
Assert.False(result.Applied);
|
||||
Assert.Equal(0, confirmation.CallCount);
|
||||
}
|
||||
|
||||
// ── GroupMembershipTools ─────────────────────────────────────────────────────────────────
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user