using System.Security.Cryptography; using System.Text.Json; using FluentAssertions; using Xunit; namespace StellaOps.Reachability.FixtureTests; public class CorpusFixtureTests { private static readonly string RepoRoot = ReachbenchFixtureTests.LocateRepoRoot(); private static readonly string CorpusRoot = Path.Combine(RepoRoot, "tests", "reachability", "corpus"); [Fact] public void ManifestExistsAndIsDeterministic() { var manifestPath = Path.Combine(CorpusRoot, "manifest.json"); File.Exists(manifestPath).Should().BeTrue("corpus manifest should exist"); using var stream = File.OpenRead(manifestPath); using var doc = JsonDocument.Parse(stream); doc.RootElement.ValueKind.Should().Be(JsonValueKind.Array); } [Fact] public void CorpusEntriesMatchManifestHashes() { var manifestPath = Path.Combine(CorpusRoot, "manifest.json"); var manifest = JsonDocument.Parse(File.ReadAllBytes(manifestPath)).RootElement.EnumerateArray().ToArray(); manifest.Should().NotBeEmpty("corpus manifest must have entries"); foreach (var entry in manifest) { var id = entry.GetProperty("id").GetString(); var language = entry.GetProperty("language").GetString(); var files = entry.GetProperty("files"); id.Should().NotBeNullOrEmpty(); language.Should().NotBeNullOrEmpty(); var caseDir = Path.Combine(CorpusRoot, language!, id!); Directory.Exists(caseDir).Should().BeTrue($"case folder missing: {caseDir}"); foreach (var fileProp in files.EnumerateObject()) { var filename = fileProp.Name; var expectedHash = fileProp.Value.GetString(); File.Exists(Path.Combine(caseDir, filename)).Should().BeTrue($"{id} missing {filename}"); var actualHash = BitConverter.ToString(SHA256.HashData(File.ReadAllBytes(Path.Combine(caseDir, filename)))).Replace("-", "").ToLowerInvariant(); actualHash.Should().Be(expectedHash, $"{id} hash mismatch for {filename}"); } } } [Fact] public void GroundTruthFilesContainRequiredFields() { var manifestPath = Path.Combine(CorpusRoot, "manifest.json"); var manifest = JsonDocument.Parse(File.ReadAllBytes(manifestPath)).RootElement.EnumerateArray().ToArray(); const string expectedSchemaVersion = "reachbench.reachgraph.truth/v1"; var allowedVariants = new[] { "reachable", "unreachable" }; foreach (var entry in manifest) { var id = entry.GetProperty("id").GetString()!; var language = entry.GetProperty("language").GetString()!; var truthPath = Path.Combine(CorpusRoot, language, id, "ground-truth.json"); File.Exists(truthPath).Should().BeTrue($"{id} missing ground-truth.json"); using var truthDoc = JsonDocument.Parse(File.ReadAllBytes(truthPath)); truthDoc.RootElement.GetProperty("schema_version").GetString().Should().Be(expectedSchemaVersion, $"{id} ground-truth schema_version mismatch"); truthDoc.RootElement.GetProperty("case_id").GetString().Should().Be(id, $"{id} ground-truth case_id must match manifest id"); var variant = truthDoc.RootElement.GetProperty("variant").GetString(); variant.Should().NotBeNullOrWhiteSpace($"{id} ground-truth must set variant"); allowedVariants.Should().Contain(variant!, $"{id} variant must be reachable|unreachable"); truthDoc.RootElement.TryGetProperty("paths", out var pathsProp).Should().BeTrue($"{id} ground-truth must include paths"); pathsProp.ValueKind.Should().Be(JsonValueKind.Array, $"{id} paths must be an array"); if (string.Equals(variant, "reachable", StringComparison.Ordinal)) { pathsProp.GetArrayLength().Should().BeGreaterThan(0, $"{id} reachable ground-truth should include at least one path"); } foreach (var path in pathsProp.EnumerateArray()) { path.ValueKind.Should().Be(JsonValueKind.Array, $"{id} each path must be an array"); path.GetArrayLength().Should().BeGreaterThan(0, $"{id} each path must contain at least one symbol"); foreach (var segment in path.EnumerateArray()) { segment.ValueKind.Should().Be(JsonValueKind.String, $"{id} path segments must be strings"); segment.GetString().Should().NotBeNullOrWhiteSpace($"{id} path segments must be non-empty strings"); } } } } }