Neuer Tab in der Gruppendetailansicht (LearningGroup.IsClassTeacher-Flag), zeigt Klassenbucheinträge anderer Lehrkräfte sowie Fehlzeiten der ganzen Klasse über alle Fächer, pro Schüler*in und Tag zusammengefasst. Beides rein lesend, kein Übernehmen in lokale Daten. Fehlzeiten-Bericht (AbsencePerStudent) adressiert die Klasse über "KL"+getKlassen-ID, automatisch aufgelöst - noch nicht mit echtem WebUntis-Zugang gegengeprüft (siehe TODO.md). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
89 lines
4.7 KiB
C#
89 lines
4.7 KiB
C#
using System.Net;
|
|
using System.Text;
|
|
using LehrerApp.Desktop.Services;
|
|
using Xunit;
|
|
|
|
namespace LehrerApp.Desktop.Tests;
|
|
|
|
public sealed class WebUntisIntegrationServiceTests
|
|
{
|
|
[Fact]
|
|
public async Task ConnectUndAbruf_GehenDirektAnWebUntisUndVerwendenDieselbeSession()
|
|
{
|
|
var handler = new QueueHandler(
|
|
Json("{\"result\":{\"sessionId\":\"desktop-session\"}}"),
|
|
Json("{\"result\":[{\"id\":1,\"name\":\"2026/27\",\"startDate\":20260801,\"endDate\":20270731}]}"),
|
|
Json("{\"result\":[{\"id\":42,\"name\":\"KUE\",\"longName\":\"Kürbis\",\"active\":true}]}"),
|
|
Json("{\"result\":{}}"));
|
|
var settings = TestSupport.BuildWebUntisSettingsService();
|
|
await using var service = new WebUntisIntegrationService(new HttpClient(handler), settings);
|
|
|
|
await service.ConnectAsync(new WebUntisCredentials(
|
|
"bk-ostvest", "arche.webuntis.com", "lehrkraft", "geheim"));
|
|
var teachers = await service.GetTeachersAsync();
|
|
|
|
Assert.Equal("KUE", Assert.Single(teachers).Name);
|
|
Assert.Equal(3, handler.Requests.Count);
|
|
Assert.All(handler.Requests, request =>
|
|
Assert.StartsWith("https://arche.webuntis.com/WebUntis/", request.Uri));
|
|
Assert.DoesNotContain(handler.Requests, request => request.Uri.Contains("/api/webuntis"));
|
|
Assert.Single(handler.Requests, request => request.Body.Contains("\"method\":\"authenticate\""));
|
|
}
|
|
|
|
[Fact]
|
|
public async Task GetClassAbsencesAsync_LoestKlasseNamensbasiertAufUndAdressiertSieAlsKLPlusId()
|
|
{
|
|
var csv = Encoding.UTF8.GetBytes(
|
|
"Schüler*innen\tExterne Id\tText\tKlasse\tDatum\tWochentag\tFehlstd.\tFehlmin.\tLehrkraft\tFach\t" +
|
|
"Abwesenheitsgrund\tText\tENr\tErledigt\tAbwesenheit zählt\tEntschuldigungstext\tStundennr.\tStatus\tFehltage\r\n" +
|
|
"Muster Max\t12345\t\t10c\t24.08.26\tMo.\t1\t45\tHED\tChe\tAbsent\tKrank gemeldet\t555\t24.08.26\ttrue\t\t3\tnicht entsch.\t1\r\n");
|
|
var handler = new QueueHandler(
|
|
Json("{\"result\":{\"sessionId\":\"desktop-session\"}}"), // authenticate (ConnectAsync)
|
|
Json("{\"result\":[{\"id\":7,\"name\":\"2026/27\",\"startDate\":20260801,\"endDate\":20270731}]}"), // getSchoolyears (ConnectAsync-Validierung)
|
|
Json("{\"result\":[{\"id\":7,\"name\":\"2026/27\",\"startDate\":20260801,\"endDate\":20270731}]}"), // getSchoolyears (ResolveClassIdAsync)
|
|
Json("{\"result\":[{\"id\":874,\"name\":\"10c\"}]}"), // getKlassen
|
|
Json("{\"data\":{\"finished\":true,\"error\":false," +
|
|
"\"reportParams\":\"get=rpt.tmp&name=AbsencePerStudent&format=csv\"}}"),
|
|
new HttpResponseMessage(HttpStatusCode.OK) { Content = new ByteArrayContent(csv) },
|
|
Json("{\"result\":{}}")); // logout (Dispose)
|
|
var settings = TestSupport.BuildWebUntisSettingsService();
|
|
await using var service = new WebUntisIntegrationService(new HttpClient(handler), settings);
|
|
|
|
await service.ConnectAsync(new WebUntisCredentials(
|
|
"bk-ostvest", "arche.webuntis.com", "lehrkraft", "geheim"));
|
|
var entries = await service.GetClassAbsencesAsync("10c",
|
|
new DateOnly(2026, 8, 24), new DateOnly(2026, 8, 28));
|
|
|
|
var entry = Assert.Single(entries);
|
|
Assert.Equal("Muster Max", entry.StudentName);
|
|
Assert.Equal("nicht entsch.", entry.Status);
|
|
// Zweimal getSchoolyears: einmal als ConnectAsync-Validierung, einmal in ResolveClassIdAsync.
|
|
Assert.Equal(2, handler.Requests.Count(request => request.Body.Contains("\"method\":\"getSchoolyears\"")));
|
|
Assert.Single(handler.Requests, request => request.Body.Contains("\"method\":\"getKlassen\""));
|
|
Assert.Single(handler.Requests, request =>
|
|
request.Uri.Contains("reports.do?name=AbsencePerStudent") &&
|
|
request.Uri.Contains("klasseOrStudentgroupId=KL874"));
|
|
}
|
|
|
|
private static HttpResponseMessage Json(string json) => new(HttpStatusCode.OK)
|
|
{
|
|
Content = new StringContent(json, Encoding.UTF8, "application/json"),
|
|
};
|
|
|
|
private sealed class QueueHandler(params HttpResponseMessage[] responses) : HttpMessageHandler
|
|
{
|
|
private readonly Queue<HttpResponseMessage> _responses = new(responses);
|
|
public List<CapturedRequest> Requests { get; } = [];
|
|
|
|
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
|
|
CancellationToken cancellationToken)
|
|
{
|
|
Requests.Add(new(request.RequestUri?.ToString() ?? "",
|
|
request.Content is null ? "" : await request.Content.ReadAsStringAsync(cancellationToken)));
|
|
return _responses.Dequeue();
|
|
}
|
|
}
|
|
|
|
private sealed record CapturedRequest(string Uri, string Body);
|
|
}
|