using System.Net.Http.Json; using System.Text.Json; using GerbilManagerWebAPI.Models; using Microsoft.Extensions.DependencyInjection; namespace GerbilManager.Tests; /// /// REFS: POST /refs/resolve löst getippte Kurz-Verweise (tier|kontakt|wurf|gehege) auf. /// - exakter (volle GUID) und kurzer (Präfix) Treffer je Typ /// - mehrdeutiges Präfix (mehrere Treffer) => id=null (nicht geraten) /// - unbekannter Code / unbekannter Typ => id=null /// Entitäten werden mit deterministischen GUIDs direkt in die SQLite-Test-DB geseedet, /// damit Präfixe und Mehrdeutigkeit kontrolliert getestet werden können. /// public class RefsEndpointTests : IClassFixture { private readonly ApiFactory _factory; public RefsEndpointTests(ApiFactory factory) => _factory = factory; // Deterministische GUIDs: zwei Tiere mit GEMEINSAMEM 6-Hex-Präfix "abcdef" (mehrdeutig), // sowie je eine eindeutige Entität pro Typ. private static readonly Guid TierA = new("abcdef00-0000-0000-0000-000000000001"); private static readonly Guid TierB = new("abcdef00-0000-0000-0000-000000000002"); private static readonly Guid Kontakt = new("11112222-3333-4444-5555-666677778888"); private static readonly Guid Wurf = new("99990000-aaaa-bbbb-cccc-ddddeeeeffff"); private static readonly Guid Gehege = new("12345678-9abc-def0-1234-567890abcdef"); private async Task SeedAsync() { using var scope = _factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); if (db.Gerbils.Any(g => g.Id == TierA)) return; // einmalig (geteilte Fixture) db.Gerbils.Add(new Gerbil { Id = TierA, Name = "Krümel", Gender = Gender.female }); db.Gerbils.Add(new Gerbil { Id = TierB, Name = "Möhre", Gender = Gender.male }); db.Contacts.Add(new Contact { Id = Kontakt, Name = "Erika Musterfrau" }); db.Litters.Add(new Litter { Id = Wurf, Name = "Wurf X", Date = new DateOnly(2026, 1, 1) }); db.Enclosures.Add(new Enclosure { Id = Gehege, Name = "Terrarium 1" }); await db.SaveChangesAsync(); } private async Task> Resolve(params object[] refs) { var resp = await _factory.CreateClient() .PostAsJsonAsync("/refs/resolve", new { refs }); resp.EnsureSuccessStatusCode(); var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()); return doc.RootElement.EnumerateArray().ToList(); } [Fact] public async Task Resolves_full_guid_per_type() { await SeedAsync(); var rows = await Resolve( new { type = "tier", code = TierA.ToString() }, new { type = "kontakt", code = Kontakt.ToString() }, new { type = "wurf", code = Wurf.ToString() }, new { type = "gehege", code = Gehege.ToString() }); Assert.Equal(TierA.ToString(), rows[0].GetProperty("id").GetString()); Assert.Equal("Krümel", rows[0].GetProperty("name").GetString()); Assert.Equal(Kontakt.ToString(), rows[1].GetProperty("id").GetString()); Assert.Equal("Erika Musterfrau", rows[1].GetProperty("name").GetString()); Assert.Equal(Wurf.ToString(), rows[2].GetProperty("id").GetString()); Assert.Equal("Wurf X", rows[2].GetProperty("name").GetString()); Assert.Equal(Gehege.ToString(), rows[3].GetProperty("id").GetString()); Assert.Equal("Terrarium 1", rows[3].GetProperty("name").GetString()); } [Fact] public async Task Resolves_short_prefix_when_unique() { await SeedAsync(); // 8-Hex-Kurz-ID des Kontakts ("11112222") ist eindeutig. var rows = await Resolve(new { type = "kontakt", code = "11112222" }); Assert.Equal(Kontakt.ToString(), rows[0].GetProperty("id").GetString()); Assert.Equal("Erika Musterfrau", rows[0].GetProperty("name").GetString()); } [Fact] public async Task Tolerates_dashes_and_case_in_code() { await SeedAsync(); // Gemischte Schreibweise + Bindestriche im Kurz-Code des Geheges. var rows = await Resolve(new { type = "gehege", code = "1234-5678" }); Assert.Equal(Gehege.ToString(), rows[0].GetProperty("id").GetString()); Assert.Equal("Terrarium 1", rows[0].GetProperty("name").GetString()); } [Fact] public async Task Ambiguous_prefix_resolves_to_null() { await SeedAsync(); // "abcdef" trifft TierA UND TierB => mehrdeutig => id/name null. var rows = await Resolve(new { type = "tier", code = "abcdef" }); Assert.Equal(JsonValueKind.Null, rows[0].GetProperty("id").ValueKind); Assert.Equal(JsonValueKind.Null, rows[0].GetProperty("name").ValueKind); Assert.Equal("tier", rows[0].GetProperty("type").GetString()); Assert.Equal("abcdef", rows[0].GetProperty("code").GetString()); } [Fact] public async Task Unknown_code_and_unknown_type_resolve_to_null() { await SeedAsync(); var rows = await Resolve( new { type = "tier", code = "fffffffe" }, // unbekannter Code new { type = "schubladen", code = "11112222" }); // unbekannter Typ Assert.Equal(JsonValueKind.Null, rows[0].GetProperty("id").ValueKind); Assert.Equal(JsonValueKind.Null, rows[1].GetProperty("id").ValueKind); } [Fact] public async Task Too_short_code_resolves_to_null() { await SeedAsync(); // < 6 Hex-Zeichen wird nicht aufgelöst (auch wenn es eindeutig wäre). var rows = await Resolve(new { type = "kontakt", code = "1111" }); Assert.Equal(JsonValueKind.Null, rows[0].GetProperty("id").ValueKind); } }