feat(triage): Ticket-Fixes (Daten + Code) + prod-fähige Triage
Some checks failed
CI / Backend Tests (.NET) (push) Successful in 1m36s
CI / Frontend Tests (Node/Vite) (push) Successful in 9m35s
CI / Docker Build & Push (push) Successful in 1m28s
CI / Deploy auf TrueNAS (Custom App) (push) Failing after 3s

Daten-Fixes (conflict-decisions.json, re-ingest-stabil) für ~30 Tickets:
Merges (Jamie/Hiro/Mino/Jana/Blacky/Sakura/Malou/Socke→Marty), Eltern-Korrekturen
(Jacky/Idefix/Ichika/Roni/Ethan), Kruke→Kuke (+ Todesdatum), Targa-Wurf R14 + Druna,
Stacy/Merle/Domi/Eliza; Joghurt-Phantomwurf entfernt.

Code-Fixes:
- Gaida & alle Verstorbenen: Status wird aus Todesdatum/Abgabe abgeleitet
  (Program.cs Startup-Sweep heilt Altfälle; IngestResolved re-derived nach Freeze).
- CoCo: Scheckungsart wird bei jeder Schecke angezeigt (Platzhalter wenn leer).
- M-Wurf/Gale: über-gemergte Fremdtiere via neuem litterChildren-Override entfernt.
- renameTo eltern-verknüpfungssicher (Quell-Name im Index); dateOfDeath als Override.

Prod-fähige Triage (API):
- GET /feedback/{id} + GET /feedback?status= (kein 2-MB-Dump).
- POST /import/ingest-resolved/upload (multipart) → Ingest gegen Prod ohne SSH.

Tests: 280 Backend, 149 Frontend, alle Python, betroffene Playwright grün.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-21 00:41:43 +02:00
parent 0a4bcabd98
commit 2e7911074f
17 changed files with 749 additions and 468 deletions

View File

@@ -500,6 +500,38 @@ public class FeedbackEndpointTests : IClassFixture<ApiFactory>
}
}
[Fact]
public async Task Get_single_feedback_by_id_and_status_filter()
{
var client = _factory.CreateClient();
// Zwei Tickets mit unterscheidbaren Namen anlegen.
var open = await client.PostAsJsonAsync("/feedback", new { message = "Offenes Ticket", context = "gerbil-detail", entityName = "FilterOpen" });
var openId = JsonDocument.Parse(await open.Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetString()!;
var other = await client.PostAsJsonAsync("/feedback", new { message = "Zweites Ticket", context = "gerbil-detail", entityName = "FilterOther" });
var otherId = JsonDocument.Parse(await other.Content.ReadAsStringAsync()).RootElement.GetProperty("id").GetString()!;
// Einzel-GET liefert genau dieses Ticket (mit attachments-Feld).
var single = JsonDocument.Parse(await client.GetStringAsync($"/feedback/{openId}")).RootElement;
Assert.Equal("FilterOpen", single.GetProperty("entityName").GetString());
Assert.Equal(JsonValueKind.Array, single.GetProperty("attachments").ValueKind);
// Unbekannte Id -> 404.
Assert.Equal(HttpStatusCode.NotFound, (await client.GetAsync($"/feedback/{Guid.NewGuid()}")).StatusCode);
// Zweites Ticket auf Answered setzen, dann Status-Filter prüfen.
await client.PutAsJsonAsync($"/feedback/{otherId}", new { status = "Answered" });
var answered = JsonDocument.Parse(await client.GetStringAsync("/feedback?status=Answered")).RootElement;
Assert.Contains(answered.EnumerateArray(), f => f.GetProperty("id").GetString() == otherId);
Assert.DoesNotContain(answered.EnumerateArray(), f => f.GetProperty("id").GetString() == openId);
// Kombinierter Filter (Open,Answered) enthält beide.
var both = JsonDocument.Parse(await client.GetStringAsync("/feedback?status=Open,Answered")).RootElement;
var ids = both.EnumerateArray().Select(f => f.GetProperty("id").GetString()).ToHashSet();
Assert.Contains(openId, ids);
Assert.Contains(otherId, ids);
}
private static object Animal(Guid id, string name, string gender, Guid contactId) => new
{
Id = id,