using System.Net; using System.Net.Http.Json; using System.Text.Json; namespace GerbilManager.Tests; /// /// QOL-ELTERN: PUT /gerbils/{id}/parents — Eltern direkt an der Tier-Akte pflegen. /// Das Datenmodell bleibt unverändert (Eltern hängen am Geburtswurf), der Endpoint /// schreibt nur dorthin bzw. legt den Träger-Wurf an, wenn es noch keinen gibt. /// public class GerbilParentsTests : IClassFixture { private readonly HttpClient _client; public GerbilParentsTests(ApiFactory factory) => _client = factory.CreateClient(); private async Task CreateGerbil(string name, string gender, string? dob = null, string? litterId = null) { var resp = await _client.PostAsJsonAsync("/gerbils", new { name, gender, dateOfBirth = dob, litterId, }); resp.EnsureSuccessStatusCode(); return JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement .GetProperty("id").GetString()!; } private async Task CreateLitter(string name, string date, string? fatherId = null, string? motherId = null) { var resp = await _client.PostAsJsonAsync("/litters", new { name, date, fatherId, motherId }); resp.EnsureSuccessStatusCode(); return JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement .GetProperty("id").GetString()!; } private async Task SetParents(string gerbilId, string? fatherId, string? motherId) { var resp = await _client.PutAsJsonAsync($"/gerbils/{gerbilId}/parents", new { fatherId, motherId }); resp.EnsureSuccessStatusCode(); return JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement; } private async Task GetLitter(string litterId) => JsonDocument.Parse(await _client.GetStringAsync($"/litters/{litterId}")).RootElement; private static string? Str(JsonElement el, string prop) => el.TryGetProperty(prop, out var v) && v.ValueKind == JsonValueKind.String ? v.GetString() : null; [Fact] public async Task Parents_of_existing_litter_are_written_to_that_litter() { var father = await CreateGerbil("Vater A", "male", "2024-01-01"); var mother = await CreateGerbil("Mutter A", "female", "2024-01-01"); var litterId = await CreateLitter("A-Wurf", "2025-03-01"); var pup = await CreateGerbil("Jungtier A", "female", "2025-03-01", litterId); var result = await SetParents(pup, father, mother); Assert.Equal(litterId, Str(result, "litterId")); Assert.False(result.GetProperty("litterCreated").GetBoolean()); Assert.False(result.GetProperty("litterAttached").GetBoolean()); var litter = await GetLitter(litterId); Assert.Equal(father, Str(litter, "fatherId")); Assert.Equal(mother, Str(litter, "motherId")); } [Fact] public async Task SiblingCount_reports_the_other_pups_the_change_applies_to() { var litterId = await CreateLitter("B-Wurf", "2025-04-01"); var pup = await CreateGerbil("Jungtier B1", "female", "2025-04-01", litterId); await CreateGerbil("Jungtier B2", "male", "2025-04-01", litterId); await CreateGerbil("Jungtier B3", "male", "2025-04-01", litterId); var mother = await CreateGerbil("Mutter B", "female", "2024-02-01"); var result = await SetParents(pup, null, mother); Assert.Equal(2, result.GetProperty("siblingCount").GetInt32()); } [Fact] public async Task Gerbil_without_litter_gets_a_hidden_carrier_litter() { var father = await CreateGerbil("Vater C", "male", "2024-03-01"); var mother = await CreateGerbil("Mutter C", "female", "2024-03-01"); var orphan = await CreateGerbil("Waise C", "female", "2025-05-01"); var result = await SetParents(orphan, father, mother); Assert.True(result.GetProperty("litterCreated").GetBoolean()); var litterId = Str(result, "litterId")!; Assert.Equal("Wurf von Vater C + Mutter C", Str(result, "litterName")); var litter = await GetLitter(litterId); Assert.Equal(father, Str(litter, "fatherId")); Assert.Equal(mother, Str(litter, "motherId")); // Nur zur Abstammung — der Hilfs-Wurf darf die Wurfchronik nicht zumüllen. Assert.False(litter.GetProperty("showInChronicle").GetBoolean()); // Wurfdatum = Geburtsdatum des Tiers. Assert.Equal("2025-05-01", Str(litter, "date")); // Das Tier hängt jetzt an diesem Wurf. var gerbil = JsonDocument.Parse(await _client.GetStringAsync($"/gerbils/{orphan}")).RootElement; Assert.Equal(litterId, Str(gerbil, "litterId")); } [Fact] public async Task Same_parents_and_same_dob_attach_to_the_existing_litter_instead_of_duplicating() { var father = await CreateGerbil("Vater D", "male", "2024-04-01"); var mother = await CreateGerbil("Mutter D", "female", "2024-04-01"); var first = await CreateGerbil("Waise D1", "female", "2025-06-01"); var second = await CreateGerbil("Waise D2", "male", "2025-06-01"); var created = await SetParents(first, father, mother); Assert.True(created.GetProperty("litterCreated").GetBoolean()); var attached = await SetParents(second, father, mother); Assert.False(attached.GetProperty("litterCreated").GetBoolean()); Assert.True(attached.GetProperty("litterAttached").GetBoolean()); // Beide sind jetzt Geschwister im selben Wurf. Assert.Equal(Str(created, "litterId"), Str(attached, "litterId")); Assert.Equal(1, attached.GetProperty("siblingCount").GetInt32()); } [Fact] public async Task Clearing_parents_nulls_them_on_the_litter() { var father = await CreateGerbil("Vater E", "male", "2024-05-01"); var litterId = await CreateLitter("E-Wurf", "2025-07-01", father); var pup = await CreateGerbil("Jungtier E", "female", "2025-07-01", litterId); await SetParents(pup, null, null); var litter = await GetLitter(litterId); Assert.Equal(JsonValueKind.Null, litter.GetProperty("fatherId").ValueKind); Assert.Equal(JsonValueKind.Null, litter.GetProperty("motherId").ValueKind); } [Fact] public async Task No_litter_and_no_parents_is_a_no_op() { var orphan = await CreateGerbil("Waise F", "female", "2025-08-01"); var result = await SetParents(orphan, null, null); Assert.Equal(JsonValueKind.Null, result.GetProperty("litterId").ValueKind); Assert.False(result.GetProperty("litterCreated").GetBoolean()); var gerbil = JsonDocument.Parse(await _client.GetStringAsync($"/gerbils/{orphan}")).RootElement; Assert.Equal(JsonValueKind.Null, gerbil.GetProperty("litterId").ValueKind); } [Fact] public async Task Wrong_gender_parent_returns_400_InvalidParentGender() { var female = await CreateGerbil("Weibchen G", "female", "2024-06-01"); var pup = await CreateGerbil("Jungtier G", "male", "2025-09-01"); var resp = await _client.PutAsJsonAsync($"/gerbils/{pup}/parents", new { fatherId = female, motherId = (string?)null }); Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode); Assert.Contains("InvalidParentGender", await resp.Content.ReadAsStringAsync()); } [Fact] public async Task Self_as_parent_returns_400() { var g = await CreateGerbil("Selbstbezug H", "male", "2025-10-01"); var resp = await _client.PutAsJsonAsync($"/gerbils/{g}/parents", new { fatherId = g, motherId = (string?)null }); Assert.Equal(HttpStatusCode.BadRequest, resp.StatusCode); } [Fact] public async Task Unknown_gerbil_returns_404() { var resp = await _client.PutAsJsonAsync($"/gerbils/{Guid.NewGuid()}/parents", new { fatherId = (string?)null, motherId = (string?)null }); Assert.Equal(HttpStatusCode.NotFound, resp.StatusCode); } }