- 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>
45 lines
1.8 KiB
C#
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);
|
|
}
|
|
}
|