Files
GerbilManager/GerbilManager.Tests/RefsEndpointTests.cs
Gulum 00dba91a5c feat(tickets): Kurz-Verweise (Shortlinks) im Ticket-Text auflösen
Die KI kann im Text `tier:<8hex>` / `kontakt:` / `wurf:` / `gehege:` schreiben;
Frontend löst per POST /refs/resolve (Präfix-Match, DB-agnostisch) zu Name +
internem Link auf. Reine KI-Schreibhilfe — kein Nutzer-Button.

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

124 lines
5.6 KiB
C#

using System.Net.Http.Json;
using System.Text.Json;
using GerbilManagerWebAPI.Models;
using Microsoft.Extensions.DependencyInjection;
namespace GerbilManager.Tests;
/// <summary>
/// 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.
/// </summary>
public class RefsEndpointTests : IClassFixture<ApiFactory>
{
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<ApplicationContext>();
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<List<JsonElement>> 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);
}
}