Files
GerbilManager/GerbilManager.Tests/PushEndpointTests.cs
Gulum f7a1a7724b feat(tickets): Web-Push-Benachrichtigungen (PWA)
- Die Züchterin kann auf der Tickets-Seite Push-Benachrichtigungen aktivieren
  (🔔-Schalter). Service Worker ist BEWUSST push-only (kein fetch/Caching), damit das
  Laden der App nie beeinträchtigt wird.
- Backend: WebPush-Bibliothek + VAPID; PushNotifier sendet an alle Abos, räumt veraltete
  (404/410) auf. Endpoints: GET /push/vapid-public-key, POST /push/subscribe|unsubscribe.
- Ausgelöst über ein notify-Flag auf PUT /feedback: NUR die KI setzt es (z. B. neue
  Rückfrage / Ticket gelöst) → keine Selbst-Pushes durch Aktionen der Züchterin. Text wird
  aus dem Status abgeleitet, Klick öffnet das Ticket (?focus=).
- Schalter/Logik blenden sich aus, wenn das Gerät kein Push kann oder der Server keine
  VAPID-Schlüssel hat (z. B. e2e-Mock).

Migration WebPushSubscriptions. VAPID-Schlüssel in appsettings.json (lokale Single-User-App
im Heimnetz — bewusste, vertretbare Vereinfachung). Tests: 264 Backend grün (+Push-Endpoints),
e2e Tickets Desktop grün, vitest 149.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 11:32:26 +02:00

45 lines
1.8 KiB
C#

using System.Net;
using System.Net.Http.Json;
using System.Text.Json;
namespace GerbilManager.Tests;
/// <summary>
/// WEB-PUSH: VAPID-Public-Key abrufen + Abo speichern/entfernen.
/// (Das tatsächliche Versenden wird nicht getestet — es geht an externe Push-Dienste.)
/// </summary>
public class PushEndpointTests : IClassFixture<ApiFactory>
{
private readonly ApiFactory _factory;
public PushEndpointTests(ApiFactory factory) => _factory = factory;
[Fact]
public async Task Vapid_public_key_is_exposed()
{
var client = _factory.CreateClient();
var doc = JsonDocument.Parse(await client.GetStringAsync("/push/vapid-public-key")).RootElement;
Assert.True(doc.GetProperty("enabled").GetBoolean());
Assert.False(string.IsNullOrWhiteSpace(doc.GetProperty("publicKey").GetString()));
}
[Fact]
public async Task Subscribe_is_idempotent_and_unsubscribe_works()
{
var client = _factory.CreateClient();
var endpoint = $"https://push.example.com/{Guid.NewGuid()}";
var body = new { endpoint, p256dh = "abc123", auth = "def456" };
var first = await client.PostAsJsonAsync("/push/subscribe", body);
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
// Erneutes Abo mit derselben Endpoint-URL -> weiterhin OK (Upsert, kein Duplikat).
var second = await client.PostAsJsonAsync("/push/subscribe", body);
Assert.Equal(HttpStatusCode.OK, second.StatusCode);
var bad = await client.PostAsJsonAsync("/push/subscribe", new { endpoint = "", p256dh = "", auth = "" });
Assert.Equal(HttpStatusCode.BadRequest, bad.StatusCode);
var unsub = await client.PostAsJsonAsync("/push/unsubscribe", new { endpoint });
Assert.Equal(HttpStatusCode.NoContent, unsub.StatusCode);
}
}