using System.Text.Json; using System.Text.RegularExpressions; using System.Xml.Linq; namespace FinalFactory.Rendezvous.Tests.Documentation; public sealed partial class DocumentationContractTests { private static readonly string[] IncidentScenarios = [ "Abuse or authentication spike", "Signing key or issuer compromise", "Targeted listing or publisher revocation", "Planned restart or crash recovery", "Release rollback", "Capacity saturation", "Privacy or telemetry incident", "Dependency or base-image upgrade", ]; [Fact] public void TestClientGuideDocumentsTheExecutableSuccessAndFailureContracts() { string root = FindRepositoryRoot(); string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "test-client.md")); Assert.Contains("space-game --environment smoke --region local --protocol 1", guide, StringComparison.Ordinal); Assert.Contains("mint-local-publisher-credential.sh", guide, StringComparison.Ordinal); Assert.Contains("join.connected", guide, StringComparison.Ordinal); Assert.Contains("join.direct-traffic", guide, StringComparison.Ordinal); Assert.Contains("browse.completed", guide, StringComparison.Ordinal); Assert.Contains("--script --json", guide, StringComparison.Ordinal); Assert.Contains("--run-seconds 60", guide, StringComparison.Ordinal); Assert.Contains("for attempt in {1..45}", guide, StringComparison.Ordinal); Assert.Contains("before the terminal-3", guide, StringComparison.Ordinal); Assert.Contains("test \"$status\" -eq 11", guide, StringComparison.Ordinal); Assert.Contains("no relay", guide, StringComparison.OrdinalIgnoreCase); Assert.Contains("cannot guarantee", guide, StringComparison.OrdinalIgnoreCase); Assert.DoesNotMatch(ReusableCredential(), guide); } [Fact] public void LocalCredentialHelperIsFixedScopeAndSmokeDelegatesToIt() { string root = FindRepositoryRoot(); string helper = File.ReadAllText(Path.Combine(root, "scripts", "mint-local-publisher-credential.sh")); string smoke = File.ReadAllText(Path.Combine(root, "scripts", "smoke-deployment.sh")); Assert.Contains("if (( $# != 0 ));", helper, StringComparison.Ordinal); Assert.Contains("\"gameId\": \"space-game\"", helper, StringComparison.Ordinal); Assert.Contains("\"environmentId\": \"smoke\"", helper, StringComparison.Ordinal); Assert.Contains("\"regions\": [\"local\"]", helper, StringComparison.Ordinal); Assert.Contains("now + 600", helper, StringComparison.Ordinal); Assert.Contains("stat.S_ISLNK", helper, StringComparison.Ordinal); Assert.Contains("parent.st_mode & 0o077", helper, StringComparison.Ordinal); Assert.Contains("metadata.st_mode & 0o022", helper, StringComparison.Ordinal); Assert.Contains("metadata.st_nlink != 1", helper, StringComparison.Ordinal); Assert.Contains("mint-local-publisher-credential.sh", smoke, StringComparison.Ordinal); Assert.DoesNotContain("hexkey:", smoke, StringComparison.Ordinal); Assert.DoesNotContain("openssl dgst", smoke, StringComparison.Ordinal); } [Fact] public void EveryIncidentRunbookHasDetectContainRecoverAndVerifyGates() { string root = FindRepositoryRoot(); string runbooks = File.ReadAllText(Path.Combine(root, "docs", "operations", "incident-runbooks.md")); for (int index = 0; index < IncidentScenarios.Length; index++) { string heading = $"## {IncidentScenarios[index]}"; int start = runbooks.IndexOf(heading, StringComparison.Ordinal); Assert.True(start >= 0, $"Missing incident runbook heading: {heading}"); int end = index + 1 < IncidentScenarios.Length ? runbooks.IndexOf($"## {IncidentScenarios[index + 1]}", start, StringComparison.Ordinal) : runbooks.Length; Assert.True(end > start, $"Could not find the end of runbook: {heading}"); string scenario = runbooks[start..end]; Assert.Contains("### Detect", scenario, StringComparison.Ordinal); Assert.Contains("### Contain", scenario, StringComparison.Ordinal); Assert.Contains("### Recover", scenario, StringComparison.Ordinal); Assert.Contains("### Verify", scenario, StringComparison.Ordinal); } Assert.DoesNotMatch(ReusableCredential(), runbooks); } [Fact] public void DocumentedOperatorOperationsAndBodiesMatchTheReleasedOpenApi() { string root = FindRepositoryRoot(); string runbooks = File.ReadAllText(Path.Combine(root, "docs", "operations", "incident-runbooks.md")); using JsonDocument openApi = JsonDocument.Parse(File.ReadAllText( Path.Combine(root, "docs", "api", "rendezvous-v1.json"))); JsonElement paths = openApi.RootElement.GetProperty("paths"); (string Method, string Path)[] documentedOperations = OperatorRoute().Matches(runbooks) .Cast() .Select(static match => ( match.Groups["method"].Value.ToLowerInvariant(), match.Groups["path"].Value)) .Distinct() .ToArray(); Assert.NotEmpty(documentedOperations); Assert.All(documentedOperations, operation => Assert.True( paths.TryGetProperty(operation.Path, out JsonElement path) && path.TryGetProperty(operation.Method, out _), $"OpenAPI does not contain {operation.Method.ToUpperInvariant()} {operation.Path}.")); Match[] actions = OperatorAction().Matches(runbooks).Cast().ToArray(); Assert.Equal(4, actions.Length); foreach (Match action in actions) { string method = action.Groups["method"].Value.ToLowerInvariant(); string path = action.Groups["path"].Value; using JsonDocument body = JsonDocument.Parse(action.Groups["body"].Value); string reference = paths.GetProperty(path) .GetProperty(method) .GetProperty("requestBody") .GetProperty("content") .GetProperty("application/json") .GetProperty("schema") .GetProperty("$ref") .GetString()!; string schemaName = reference["#/components/schemas/".Length..]; string[] required = openApi.RootElement.GetProperty("components") .GetProperty("schemas") .GetProperty(schemaName) .GetProperty("required") .EnumerateArray() .Select(static property => property.GetString()!) .Order(StringComparer.Ordinal) .ToArray(); string[] documented = body.RootElement.EnumerateObject() .Select(static property => property.Name) .Order(StringComparer.Ordinal) .ToArray(); Assert.Equal(required, documented); } } [Fact] public void SdkGuideMatchesTheReleasedPackageAndTransportMatrix() { string root = FindRepositoryRoot(); string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "sdk-seams.md")); using JsonDocument compatibility = JsonDocument.Parse(File.ReadAllText( Path.Combine(root, "docs", "releases", "compatibility.json"))); JsonElement matrix = compatibility.RootElement; JsonElement packages = matrix.GetProperty("packages"); string clientVersion = packages.GetProperty("FinalFactory.Rendezvous.Client").GetString()!; string contractsVersion = packages.GetProperty("FinalFactory.Rendezvous.Contracts").GetString()!; string liteNetLibVersion = matrix.GetProperty("transport").GetProperty("version").GetString()!; string clientFramework = XDocument.Load(Path.Combine( root, "src", "FinalFactory.Rendezvous.Client", "FinalFactory.Rendezvous.Client.csproj")) .Descendants("TargetFramework") .Single() .Value; int httpVersion = matrix.GetProperty("contracts").GetProperty("http")[0].GetInt32(); int udpVersion = matrix.GetProperty("contracts").GetProperty("udp")[0].GetInt32(); int ticketVersion = matrix.GetProperty("contracts").GetProperty("connectionTicket")[0].GetInt32(); Assert.Contains( $"FinalFactory.Rendezvous.Client\" Version=\"{clientVersion}\"", guide, StringComparison.Ordinal); Assert.Contains( $"FinalFactory.Rendezvous.Contracts\" Version=\"{contractsVersion}\"", guide, StringComparison.Ordinal); Assert.Contains($"targets `{clientFramework}`", guide, StringComparison.Ordinal); Assert.Contains($"LiteNetLib `{liteNetLibVersion}`", guide, StringComparison.Ordinal); Assert.Contains( "https://git.finalfactory.de/api/packages/HeiKyu/nuget/index.json", guide, StringComparison.Ordinal); Assert.Equal(httpVersion, udpVersion); Assert.Equal(httpVersion, ticketVersion); Assert.Contains($"contract version `{httpVersion}`", guide, StringComparison.Ordinal); } [GeneratedRegex(@"rv1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+", RegexOptions.CultureInvariant)] private static partial Regex ReusableCredential(); [GeneratedRegex(@"(?GET|POST) `?(?/v1/operator/[a-z/-]+)`?", RegexOptions.CultureInvariant)] private static partial Regex OperatorRoute(); [GeneratedRegex(@"\| `(?POST) (?/v1/operator/[a-z/-]+)` \| `(?\{[^`]+\})` \|", RegexOptions.CultureInvariant)] private static partial Regex OperatorAction(); private static string FindRepositoryRoot() { DirectoryInfo? current = new(AppContext.BaseDirectory); while (current is not null) { if (File.Exists(Path.Combine(current.FullName, "Rendezvous.slnx"))) { return current.FullName; } current = current.Parent; } throw new InvalidOperationException("Could not locate the repository root."); } }