using System.Text.Json;
using GerbilManagerWebAPI.Import;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Xunit.Abstractions;
namespace GerbilManager.Tests
{
///
/// Golden regression fixture: ingests the REAL tools/import/output/resolved_import.json
/// (gitignored, local only) and asserts that every verified key animal's Akte still matches its
/// frozen golden snapshot in tools/import/verified-golden.json (committed fixture).
///
/// Both files are optional: if EITHER is missing the test skips cleanly (no failure), so it is a
/// no-op on CI / fresh clones but a real guard on the breeder's machine once she has verified
/// animals and exported the fixture.
///
public class VerifiedGoldenTests
{
private readonly ITestOutputHelper _out;
public VerifiedGoldenTests(ITestOutputHelper output) => _out = output;
private sealed record GoldenEntry(Guid GerbilId, string? Name, DateTimeOffset? VerifiedAt, GerbilSnapshot? Snapshot);
private static readonly JsonSerializerOptions Web = new(JsonSerializerDefaults.Web);
private static string? FindRepoRoot()
{
var dir = new DirectoryInfo(AppContext.BaseDirectory);
while (dir is not null)
{
if (Directory.Exists(Path.Combine(dir.FullName, "tools", "import")))
return dir.FullName;
dir = dir.Parent;
}
return null;
}
[Fact]
public async Task Verified_animals_still_match_their_golden_snapshot()
{
var repoRoot = FindRepoRoot();
if (repoRoot is null)
{
_out.WriteLine("SKIP: repo root (tools/import) not found from the test host.");
return;
}
var goldenPath = Path.Combine(repoRoot, "tools", "import", "verified-golden.json");
var outputDir = Path.Combine(repoRoot, "tools", "import", "output");
var resolvedPath = Path.Combine(outputDir, "resolved_import.json");
if (!File.Exists(goldenPath))
{
_out.WriteLine($"SKIP: golden fixture not present ({goldenPath}).");
return;
}
if (!File.Exists(resolvedPath))
{
_out.WriteLine($"SKIP: resolved_import.json not present ({resolvedPath}) — run the import pipeline.");
return;
}
List? golden;
try
{
golden = JsonSerializer.Deserialize>(await File.ReadAllTextAsync(goldenPath), Web);
}
catch (Exception ex)
{
_out.WriteLine($"SKIP: could not parse golden fixture: {ex.Message}");
return;
}
if (golden is null || golden.Count == 0)
{
_out.WriteLine("SKIP: golden fixture is empty.");
return;
}
// Ingest the real resolved_import.json into an InMemory DB.
var opts = new DbContextOptionsBuilder()
.UseInMemoryDatabase("verified-golden-" + Guid.NewGuid().ToString("N"))
.Options;
using var db = new ApplicationContext(opts);
db.Database.EnsureCreated();
var photoDir = Path.Combine(Path.GetTempPath(), "verified-golden-photos-" + Guid.NewGuid().ToString("N"));
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary
{
{ "Import:SourcePath", outputDir },
{ "Photos:RootPath", photoDir },
})
.Build();
try
{
var result = await new IngestResolvedService(db, config, null!).RunAsync();
Assert.StartsWith("Ingestion successful!", result);
}
catch (Exception ex)
{
_out.WriteLine($"SKIP: ingest of the real resolved_import.json failed: {ex.Message}");
return;
}
finally
{
try { Directory.Delete(photoDir, recursive: true); } catch { }
}
var mismatches = new List();
int checkedCount = 0;
foreach (var entry in golden)
{
if (entry.Snapshot is null) continue;
checkedCount++;
GerbilSnapshot? actual;
try
{
actual = await GerbilSnapshotService.BuildSnapshotAsync(db, entry.GerbilId);
}
catch (Exception ex)
{
mismatches.Add($"• {entry.Name ?? entry.GerbilId.ToString()}: Akte konnte nicht gebaut werden — {ex.Message}");
continue;
}
if (actual is null)
{
mismatches.Add($"• {entry.Name ?? entry.GerbilId.ToString()}: fehlt im Import (verifiziertes Tier verschwunden).");
continue;
}
var diffs = GerbilSnapshotService.Diff(entry.Snapshot, actual);
if (diffs.Count > 0)
{
var fields = string.Join("; ", diffs.Select(d => $"{d.Path}: golden='{d.GoldenValue}' ≠ import='{d.OtherValue}'"));
mismatches.Add($"• {entry.Name ?? entry.GerbilId.ToString()}: {diffs.Count} Abweichung(en) — {fields}");
}
}
_out.WriteLine($"Golden-Fixture geprüft: {checkedCount} verifizierte Tiere, {mismatches.Count} abweichend.");
if (mismatches.Count > 0)
{
var report = $"Golden-Regression fehlgeschlagen — {mismatches.Count} von {checkedCount} verifizierten Tieren weichen vom eingefrorenen Snapshot ab:\n"
+ string.Join("\n", mismatches);
Assert.Fail(report);
}
}
}
}