using GerbilManagerWebAPI.Import; using GerbilManagerWebAPI.Models; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using System.Text.Json; namespace GerbilManager.Tests { public class IngestResolvedServiceTests : IDisposable { private readonly string _dir; private readonly string _resolvedJsonPath; public IngestResolvedServiceTests() { _dir = Path.Combine(Path.GetTempPath(), "ingest-resolved-" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_dir); _resolvedJsonPath = Path.Combine(_dir, "resolved_import.json"); var contactId = Guid.NewGuid(); var fatherId = Guid.NewGuid(); var motherId = Guid.NewGuid(); var litterId = Guid.NewGuid(); var photoId = Guid.NewGuid(); var data = new { Contacts = new[] { new { Id = contactId, Name = "Test Breeder", ZuchtName = "Test Zucht", City = "Test City", Email = "test@example.com", Homepage = "", Phone = "", Address = "", Provenance = (string?)"{\"sourceFiles\":[\"Stammbaum\"],\"mergedRecordCount\":1,\"notes\":[\"als Züchter erkannt\"]}" } }, Litters = new[] { new { Id = litterId, Name = "Wurf A", Date = "2026-01-01", TotalBorn = 5, DeathsWithin8Weeks = 0, FatherId = fatherId, MotherId = motherId, ExpectedGoHomeDate = (string?)null, Notes = "Test litter notes", PairingCode = "PC01", ExternalRef = "ext-litter-1", LitterLetter = "A", Provenance = (string?)"{\"sourceFiles\":[\"Wurfchronik-Detail.docx\"],\"mergedRecordCount\":1,\"fromWurfchronik\":true,\"notes\":[\"aus Wurfchronik\"]}" } }, Gerbils = new[] { new { Id = fatherId, Name = "Papa", Gender = "male", Status = "Breeding", LitterId = (Guid?)null, OriginContactId = contactId, ReceiverContactId = (Guid?)null, EnclosureId = (Guid?)null, ColorVarietyId = new Guid("00000000-0000-0000-0000-000000000006"), // Schwarz 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-papa", RawImportData = "{}", OriginBreeder = "Test Zucht", NameSearch = "papa", CharacterTraits = new string[] {}, CharacterNote = (string?)null, IsDeaf = false, IsResident = true, Provenance = (string?)"{\"sourceFiles\":[\"Stammbaum von Papa.xlsx\",\"Wurfchronik-Detail.docx\"],\"mergedRecordCount\":2,\"fromWurfchronik\":true,\"notes\":[\"aus 2 Datensätzen zusammengeführt\"]}" }, new { Id = motherId, Name = "Mama", Gender = "female", Status = "Breeding", LitterId = (Guid?)null, OriginContactId = contactId, ReceiverContactId = (Guid?)null, EnclosureId = (Guid?)null, ColorVarietyId = new Guid("00000000-0000-0000-0000-000000000006"), // Schwarz 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-mama", RawImportData = "{}", OriginBreeder = "Test Zucht", NameSearch = "mama", CharacterTraits = new string[] {}, CharacterNote = (string?)null, IsDeaf = false, IsResident = true, Provenance = (string?)null } }, GerbilPhotos = new[] { new { Id = photoId, GerbilId = fatherId, FileName = "photo1.jpg", SortOrder = 0 } } }; File.WriteAllText(_resolvedJsonPath, JsonSerializer.Serialize(data)); } public void Dispose() { try { Directory.Delete(_dir, recursive: true); } catch { } } private ApplicationContext NewDb() { var opts = new DbContextOptionsBuilder() .UseInMemoryDatabase("ingest-resolved-" + Guid.NewGuid().ToString("N")) .Options; var db = new ApplicationContext(opts); db.Database.EnsureCreated(); return db; } [Fact] public async Task IngestResolved_loads_data_and_resolves_all_relations() { using var db = NewDb(); var config = new ConfigurationBuilder() .AddInMemoryCollection(new Dictionary { { "Import:SourcePath", _dir } }) .Build(); var service = new IngestResolvedService(db, config, null!); var result = await service.RunAsync(); Assert.Contains("Ingestion successful!", result); // Verify counts Assert.Equal(1, await db.Contacts.CountAsync()); Assert.Equal(1, await db.Litters.CountAsync()); Assert.Equal(2, await db.Gerbils.CountAsync()); Assert.Equal(1, await db.GerbilPhotos.CountAsync()); // Verify relations var litter = await db.Litters.SingleAsync(); var father = await db.Gerbils.SingleAsync(g => g.Name == "Papa"); var mother = await db.Gerbils.SingleAsync(g => g.Name == "Mama"); var photo = await db.GerbilPhotos.SingleAsync(); Assert.Equal(father.Id, litter.FatherId); Assert.Equal(mother.Id, litter.MotherId); Assert.Equal(father.Id, photo.GerbilId); Assert.Equal(father.OriginContactId, mother.OriginContactId); // Provenance round-trips verbatim through ingest (and stays null when absent). Assert.NotNull(father.Provenance); Assert.Contains("Stammbaum von Papa.xlsx", father.Provenance); Assert.Contains("mergedRecordCount", father.Provenance); Assert.Null(mother.Provenance); // Contact + litter provenance also round-trips through the ingest. var contact = await db.Contacts.SingleAsync(); Assert.NotNull(contact.Provenance); Assert.Contains("als Züchter erkannt", contact.Provenance); Assert.NotNull(litter.Provenance); Assert.Contains("aus Wurfchronik", litter.Provenance); Assert.Contains("fromWurfchronik", litter.Provenance); } } }