From f2a1263198ae348eb013f8770888629f36a0151a Mon Sep 17 00:00:00 2001 From: gamer147 Date: Tue, 26 May 2026 13:24:49 -0400 Subject: [PATCH] refactor(bootstrap): add seed loader + extractor scaffolding --- SVSim.Bootstrap/Data/seeds/.gitkeep | 0 SVSim.Bootstrap/Importers/SeedLoader.cs | 42 ++++++++++++++++++++ SVSim.Bootstrap/Models/Seed/.gitkeep | 0 SVSim.Bootstrap/SVSim.Bootstrap.csproj | 3 ++ SVSim.UnitTests/Importers/SeedLoaderTests.cs | 39 ++++++++++++++++++ 5 files changed, 84 insertions(+) create mode 100644 SVSim.Bootstrap/Data/seeds/.gitkeep create mode 100644 SVSim.Bootstrap/Importers/SeedLoader.cs create mode 100644 SVSim.Bootstrap/Models/Seed/.gitkeep create mode 100644 SVSim.UnitTests/Importers/SeedLoaderTests.cs diff --git a/SVSim.Bootstrap/Data/seeds/.gitkeep b/SVSim.Bootstrap/Data/seeds/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/SVSim.Bootstrap/Importers/SeedLoader.cs b/SVSim.Bootstrap/Importers/SeedLoader.cs new file mode 100644 index 0000000..ec343ed --- /dev/null +++ b/SVSim.Bootstrap/Importers/SeedLoader.cs @@ -0,0 +1,42 @@ +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace SVSim.Bootstrap.Importers; + +/// +/// Reads a JSON seed file under SVSim.Bootstrap/Data/seeds/. Replaces ImporterBase.LoadCapture. +/// Files are produced by extractors in data_dumps/extract/; the bootstrap project does not +/// transform wire formats. Missing files are non-fatal (returns empty/null) — caller decides. +/// +public static class SeedLoader +{ + private static readonly JsonSerializerOptions Options = new() + { + PropertyNamingPolicy = JsonNamingPolicy.SnakeCaseLower, + NumberHandling = JsonNumberHandling.AllowReadingFromString, + ReadCommentHandling = JsonCommentHandling.Skip, + AllowTrailingCommas = true, + }; + + public static List LoadList(string path) + { + if (!File.Exists(path)) + { + Console.Error.WriteLine($"[SeedLoader] Missing seed file: {path}"); + return new List(); + } + using var fs = File.OpenRead(path); + return JsonSerializer.Deserialize>(fs, Options) ?? new List(); + } + + public static T? LoadObject(string path) where T : class + { + if (!File.Exists(path)) + { + Console.Error.WriteLine($"[SeedLoader] Missing seed file: {path}"); + return null; + } + using var fs = File.OpenRead(path); + return JsonSerializer.Deserialize(fs, Options); + } +} diff --git a/SVSim.Bootstrap/Models/Seed/.gitkeep b/SVSim.Bootstrap/Models/Seed/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/SVSim.Bootstrap/SVSim.Bootstrap.csproj b/SVSim.Bootstrap/SVSim.Bootstrap.csproj index dc5bf95..4ae4364 100644 --- a/SVSim.Bootstrap/SVSim.Bootstrap.csproj +++ b/SVSim.Bootstrap/SVSim.Bootstrap.csproj @@ -27,6 +27,9 @@ PreserveNewest + + PreserveNewest + diff --git a/SVSim.UnitTests/Importers/SeedLoaderTests.cs b/SVSim.UnitTests/Importers/SeedLoaderTests.cs new file mode 100644 index 0000000..e74049a --- /dev/null +++ b/SVSim.UnitTests/Importers/SeedLoaderTests.cs @@ -0,0 +1,39 @@ +using SVSim.Bootstrap.Importers; + +namespace SVSim.UnitTests.Importers; + +public class SeedLoaderTests +{ + private sealed record Row(int Id, string Name); + + [Test] + public void LoadList_returns_empty_when_file_missing() + { + string path = Path.Combine(Path.GetTempPath(), $"missing-{Guid.NewGuid()}.json"); + var rows = SeedLoader.LoadList(path); + Assert.That(rows, Is.Empty); + } + + [Test] + public void LoadList_deserializes_snake_case_array() + { + string path = Path.Combine(Path.GetTempPath(), $"seed-{Guid.NewGuid()}.json"); + File.WriteAllText(path, "[{\"id\":1,\"name\":\"a\"},{\"id\":2,\"name\":\"b\"}]"); + try + { + var rows = SeedLoader.LoadList(path); + Assert.That(rows, Has.Count.EqualTo(2)); + Assert.That(rows[0].Id, Is.EqualTo(1)); + Assert.That(rows[1].Name, Is.EqualTo("b")); + } + finally { File.Delete(path); } + } + + [Test] + public void LoadObject_returns_null_when_file_missing() + { + string path = Path.Combine(Path.GetTempPath(), $"missing-{Guid.NewGuid()}.json"); + var row = SeedLoader.LoadObject(path); + Assert.That(row, Is.Null); + } +}