using System.Net; using System.Net.Http.Json; using System.Text.Json; using GerbilManagerWebAPI.Import; using GerbilManagerWebAPI.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; namespace GerbilManager.Tests; /// /// ERWERB: acquisition data per animal (purchase date/price/note). /// - POST creates, GET ?gerbilId= filters, PUT updates, DELETE removes. /// - CRITICAL: acquisition rows survive the import re-ingest wipe (loose, FK-free /// GerbilId/SourceContactId), exactly like Feedback. /// public class AcquisitionEndpointTests : IClassFixture { private readonly ApiFactory _factory; public AcquisitionEndpointTests(ApiFactory factory) => _factory = factory; [Fact] public async Task Crud_roundtrip_create_filter_update_delete() { var client = _factory.CreateClient(); var gerbilId = Guid.NewGuid(); var contactId = Guid.NewGuid(); // CREATE var resp = await client.PostAsJsonAsync("/acquisitions", new { gerbilId, sourceContactId = contactId, date = "2025-03-14", price = 25.50m, note = "Auf der Börse gekauft.", }); Assert.Equal(HttpStatusCode.Created, resp.StatusCode); var created = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()).RootElement; var id = created.GetProperty("id").GetString()!; Assert.Equal(gerbilId.ToString(), created.GetProperty("gerbilId").GetString()); Assert.Equal(25.50m, created.GetProperty("price").GetDecimal()); Assert.Equal("2025-03-14", created.GetProperty("date").GetString()); // GET ?gerbilId= returns it var listed = JsonDocument.Parse(await client.GetStringAsync($"/acquisitions?gerbilId={gerbilId}")).RootElement; Assert.Contains(listed.EnumerateArray(), a => a.GetProperty("note").GetString() == "Auf der Börse gekauft."); // a different gerbilId yields nothing var other = JsonDocument.Parse(await client.GetStringAsync($"/acquisitions?gerbilId={Guid.NewGuid()}")).RootElement; Assert.Empty(other.EnumerateArray()); // UPDATE var put = await client.PutAsJsonAsync($"/acquisitions/{id}", new { gerbilId, sourceContactId = (Guid?)null, date = "2025-04-01", price = 30m, note = "Korrigiert.", }); Assert.Equal(HttpStatusCode.NoContent, put.StatusCode); var afterPut = JsonDocument.Parse(await client.GetStringAsync($"/acquisitions/{id}")).RootElement; Assert.Equal(30m, afterPut.GetProperty("price").GetDecimal()); Assert.Equal("Korrigiert.", afterPut.GetProperty("note").GetString()); Assert.True(afterPut.GetProperty("sourceContactId").ValueKind == JsonValueKind.Null); // DELETE var del = await client.DeleteAsync($"/acquisitions/{id}"); Assert.Equal(HttpStatusCode.NoContent, del.StatusCode); var gone = await client.GetAsync($"/acquisitions/{id}"); Assert.Equal(HttpStatusCode.NotFound, gone.StatusCode); } [Fact] public async Task Acquisition_survives_ingest_wipe() { var dir = Path.Combine(Path.GetTempPath(), "acq-ingest-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(dir); try { var contactId = Guid.NewGuid(); var fatherId = Guid.NewGuid(); var motherId = Guid.NewGuid(); var litterId = Guid.NewGuid(); var data = new { Contacts = new[] { new { Id = contactId, Name = "Test Breeder", Email = "t@e.de", Phone = "", Address = "", Notes = (string?)null, IsBreeder = true, IsReceiver = false, NameSuffix = (string?)null, Provenance = (string?)null } }, Litters = new[] { new { Id = litterId, Name = "Wurf A", Date = "2026-01-01", TotalBorn = 5, DeathsWithin8Weeks = 0, FatherId = fatherId, MotherId = motherId, ExpectedGoHomeDate = (string?)null, Notes = "", PairingCode = "PC01", ExternalRef = "ext-litter-1", LitterLetter = "A" } }, Gerbils = new[] { Animal(fatherId, "Papa", "male", contactId), Animal(motherId, "Mama", "female", contactId), }, GerbilPhotos = Array.Empty(), }; File.WriteAllText(Path.Combine(dir, "resolved_import.json"), JsonSerializer.Serialize(data)); var opts = new DbContextOptionsBuilder() .UseInMemoryDatabase("acq-ingest-" + Guid.NewGuid().ToString("N")) .Options; using var db = new ApplicationContext(opts); db.Database.EnsureCreated(); // An acquisition referencing the gerbil + contact the wipe will delete. var acqId = Guid.NewGuid(); db.AcquisitionRecords.Add(new AcquisitionRecord { Id = acqId, GerbilId = fatherId, SourceContactId = contactId, Date = new DateOnly(2025, 3, 14), Price = 25.50m, Note = "Auf der Börse gekauft.", CreatedAt = DateTimeOffset.UtcNow, }); await db.SaveChangesAsync(); var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { { "Import:SourcePath", dir } }) .Build(); var result = await new IngestResolvedService(db, config, null!).RunAsync(); Assert.Contains("Ingestion successful!", result); // The acquisition row survives even though the gerbil + contact were wiped/recreated. var survivor = await db.AcquisitionRecords.SingleAsync(a => a.Id == acqId); Assert.Equal(fatherId, survivor.GerbilId); Assert.Equal(contactId, survivor.SourceContactId); Assert.Equal(25.50m, survivor.Price); Assert.Equal("Auf der Börse gekauft.", survivor.Note); Assert.Equal(1, await db.AcquisitionRecords.CountAsync()); } finally { try { Directory.Delete(dir, recursive: true); } catch { /* best effort */ } } } private static object Animal(Guid id, string name, string gender, Guid contactId) => new { Id = id, Name = name, Gender = gender, Status = "Breeding", LitterId = (Guid?)null, OriginContactId = contactId, ReceiverContactId = (Guid?)null, EnclosureId = (Guid?)null, ColorVarietyId = new Guid("00000000-0000-0000-0000-000000000006"), DateOfBirth = "2025-01-01", DateOfDeath = (string?)null, CauseOfDeath = (string?)null, GoHomeDate = (string?)null, Genotype = "aa CC DD EE GG PP spsp rere", Notes = "", ImportSource = "docx-export", ExternalRef = "ext-" + name, RawImportData = "{}", OriginBreeder = "Test Zucht", NameSearch = name.ToLowerInvariant(), CharacterTraits = Array.Empty(), CharacterNote = (string?)null, IsDeaf = false, IsResident = true, }; }