KI-Backend: Antwortretten - Funktion
CI / build-and-test (push) Canceled after 0s

This commit is contained in:
2026-09-04 12:10:42 +02:00
parent 7ef0c95a73
commit fedfceb81d
10 changed files with 435 additions and 14 deletions
@@ -1,6 +1,9 @@
using LehrerApp.Core.AiPlanning;
using LehrerApp.Core.Models;
using LehrerApp.Desktop.Services;
using LehrerApp.Desktop.ViewModels.Groups;
using System.Net;
using System.Text;
using Xunit;
namespace LehrerApp.Desktop.Tests;
@@ -15,6 +18,106 @@ public sealed class AiPlanningServiceTests
FakeCompetencyDomains competencyDomains, FakeAlternativeLessonPaths altPaths) =>
new(new HttpClient(), lessons, groups, subjects, competencyDomains, altPaths);
[Fact]
public void ParsePlanningLessons_AkzeptiertNormaleAntwortUndDeutschesDatum()
{
const string json = """
{"lessons":[{"id":null,"date":"15.09.2026","lessonNumber":2,"topic":"Redox","startTime":"08:35","phases":[]}],"summary":"ok"}
""";
var lesson = Assert.Single(AiPlanningService.ParsePlanningLessons(json));
Assert.Equal("Redox", lesson.Topic);
Assert.Equal(new DateOnly(2026, 9, 15), lesson.Date);
Assert.Equal(new TimeOnly(8, 35), lesson.StartTime);
}
[Fact]
public void ParsePlanningLessons_AkzeptiertEinzelneMarkierteStunde()
{
const string json = """
{"topic":"Nur diese Stunde","phases":[{"name":"Einstieg","durationMinutes":5,"activity":"Impuls","material":"Bild","shorthand":"UG"}]}
""";
var lesson = Assert.Single(AiPlanningService.ParsePlanningLessons(json));
Assert.Equal("Nur diese Stunde", lesson.Topic);
Assert.Single(lesson.Phases);
}
[Fact]
public void ParsePlanningLessons_UngueltigeMarkierung_LiefertVerstaendlichenFehler()
{
var error = Assert.Throws<AiBackendException>(() =>
AiPlanningService.ParsePlanningLessons("Hier kommt das JSON: { kaputt }"));
Assert.Contains("noch kein gültiges Stunden-JSON", error.Message);
}
[Fact]
public async Task RequestPlanAsync_Backendfehler_BewahrtOriginalantwortFuerRettung()
{
const string body = """
{"error":"Kein gültiges JSON.","rawResponse":"Vorspann\n{kaputt}"}
""";
var service = BuildWithResponse(HttpStatusCode.BadGateway, body);
var error = await Assert.ThrowsAsync<AiBackendException>(() =>
service.RequestPlanAsync(new Unit(), "", "token"));
Assert.Equal("Vorspann\n{kaputt}", error.RawResponse);
}
[Fact]
public async Task RequestPlanAsync_Typfehler_BewahrtEingebetteteOriginalantwortFuerRettung()
{
const string body = """
{"lessons":[{"topic":"Test","date":"kein Datum"}],"rawResponse":"DIE ORIGINALANTWORT"}
""";
var service = BuildWithResponse(HttpStatusCode.OK, body);
var error = await Assert.ThrowsAsync<AiBackendException>(() =>
service.RequestPlanAsync(new Unit(), "", "token"));
Assert.Equal("DIE ORIGINALANTWORT", error.RawResponse);
}
[Fact]
public void Rettungsdialog_ImportiertMarkierteStundeAlsNeu()
{
var group = new LearningGroup();
var unit = new Unit { GroupId = group.Id, Title = "T" };
var lessons = new FakeLessons();
var service = Build(lessons, new FakeGroups([group]), new FakeSubjects([]),
new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
var vm = new AiResponseRescueDialogViewModel(service, lessons, unit, "Rohantwort");
vm.ParseSelection("""{"topic":"Gerettete Stunde","phases":[]}""");
vm.ImportAsNewCommand.Execute(null);
Assert.True(vm.Result);
Assert.Equal("Gerettete Stunde", Assert.Single(lessons.GetByUnit(unit.Id)).Topic);
}
private static AiPlanningService BuildWithResponse(HttpStatusCode status, string body)
{
var http = new HttpClient(new StaticResponseHandler(status, body))
{
BaseAddress = new Uri("https://example.invalid/"),
};
return new AiPlanningService(http, new FakeLessons(), new FakeGroups([]), new FakeSubjects([]),
new FakeCompetencyDomains(), new FakeAlternativeLessonPaths([]));
}
private sealed class StaticResponseHandler(HttpStatusCode status, string body) : HttpMessageHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken) => Task.FromResult(new HttpResponseMessage(status)
{
Content = new StringContent(body, Encoding.UTF8, "application/json"),
});
}
[Fact]
public void BuildContext_FuelltGruppenUndFachKontext()
{