322 lines
19 KiB
C#
322 lines
19 KiB
C#
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 LocalCredentialHelperWhitelistsProvisionedGameScopesAndSmokeDelegatesToIt()
|
|
{
|
|
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("space-game)", helper, StringComparison.Ordinal);
|
|
Assert.Contains("unscouted)", helper, StringComparison.Ordinal);
|
|
Assert.Contains("KEY_ID=\"local-smoke-1\"", helper, StringComparison.Ordinal);
|
|
Assert.Contains("KEY_ID=\"local-smoke-unscouted-1\"", helper, StringComparison.Ordinal);
|
|
Assert.Contains("RENDEZVOUS_LOCAL_CREDENTIAL_GAME_ID must be space-game or unscouted", helper, StringComparison.Ordinal);
|
|
Assert.Contains("\"gameId\": game_id", 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 & 0o077", helper, StringComparison.Ordinal);
|
|
Assert.Contains("metadata.st_nlink != 1", helper, StringComparison.Ordinal);
|
|
Assert.Contains("mint-local-publisher-credential.sh", smoke, StringComparison.Ordinal);
|
|
Assert.Contains("select(.event == \"host.ready\" and .status == \"ready\")", smoke, StringComparison.Ordinal);
|
|
Assert.Contains("hasListingId", smoke, StringComparison.Ordinal);
|
|
Assert.Contains("jq -s -e", smoke, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("jq -c .", smoke, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("hexkey:", smoke, StringComparison.Ordinal);
|
|
Assert.DoesNotContain("openssl dgst", smoke, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnscoutedComposeTenantIsGameScopedAndMetadataBounded()
|
|
{
|
|
string root = FindRepositoryRoot();
|
|
using JsonDocument settings = JsonDocument.Parse(File.ReadAllText(
|
|
Path.Combine(root, "deploy", "compose", "appsettings.Production.json")));
|
|
JsonElement provisioning = settings.RootElement.GetProperty("Rendezvous").GetProperty("Provisioning");
|
|
JsonElement game = provisioning.GetProperty("Games").EnumerateArray().Single(
|
|
static item => item.GetProperty("GameId").GetString() == "unscouted");
|
|
JsonElement key = provisioning.GetProperty("SigningKeys").EnumerateArray().Single(
|
|
static item => item.GetProperty("KeyId").GetString() == "local-smoke-unscouted-1");
|
|
|
|
Assert.Equal("smoke", game.GetProperty("EnvironmentId").GetString());
|
|
Assert.Equal([1], game.GetProperty("ProtocolVersions").EnumerateArray().Select(static value => value.GetInt32()));
|
|
Assert.Equal(["mode", "mods", "world"], game.GetProperty("MetadataValueMaxBytes")
|
|
.EnumerateObject().Select(static property => property.Name).Order(StringComparer.Ordinal));
|
|
Assert.Equal(["mode", "mods", "world"], game.GetProperty("RequiredMetadataKeys")
|
|
.EnumerateArray().Select(static value => value.GetString()).Order(StringComparer.Ordinal));
|
|
Assert.Equal(3, game.GetProperty("MetadataMaxKeys").GetInt32());
|
|
Assert.Equal("DedicatedEndpointAllowed", game.GetProperty("FallbackPolicy").GetString());
|
|
Assert.Equal("unscouted", key.GetProperty("GameId").GetString());
|
|
Assert.Equal("smoke", key.GetProperty("EnvironmentId").GetString());
|
|
Assert.Equal(["DedicatedPublisher"], key.GetProperty("CredentialKinds")
|
|
.EnumerateArray().Select(static value => value.GetString()));
|
|
}
|
|
|
|
[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<Match>()
|
|
.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<Match>().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);
|
|
Assert.Contains("gameplayNetwork.ChannelsCount = 3", guide, StringComparison.Ordinal);
|
|
Assert.Contains("defaults to one QoS channel", guide, StringComparison.Ordinal);
|
|
Assert.Contains("Rendezvous does not choose, remap, or reserve", guide, StringComparison.Ordinal);
|
|
}
|
|
|
|
[Fact]
|
|
public void SpaceGamePilotEvidenceSeparatesProvenBehaviorFromOpenGates()
|
|
{
|
|
string root = FindRepositoryRoot();
|
|
string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "spacegame-pilot.md"));
|
|
string deploymentGuide = File.ReadAllText(Path.Combine(root, "docs", "deployment", "linux.md"));
|
|
using JsonDocument evidence = JsonDocument.Parse(File.ReadAllText(
|
|
Path.Combine(root, "docs", "evidence", "consumers", "spacegame.json")));
|
|
JsonElement record = evidence.RootElement;
|
|
|
|
Assert.Equal("checkpoint-pass-with-external-gates", record.GetProperty("result").GetString());
|
|
Assert.Equal("none", record.GetProperty("localRun").GetProperty("rendezvousGameplayPayloadPath").GetString());
|
|
Assert.Equal("caller-owned-litenetlib", record.GetProperty("localRun").GetProperty("gameplayTransport").GetString());
|
|
Assert.True(record.GetProperty("localRun").GetProperty("directGameplay").GetBoolean());
|
|
Assert.True(record.GetProperty("localRun").GetProperty("reconnected").GetBoolean());
|
|
Assert.True(record.GetProperty("linuxRun").GetProperty("freshExport").GetBoolean());
|
|
Assert.False(record.GetProperty("linuxRun").GetProperty("sourceDirty").GetBoolean());
|
|
Assert.Equal(31, record.GetProperty("verification").GetProperty("debugTests").GetProperty("passed").GetInt32());
|
|
Assert.Equal(31, record.GetProperty("verification").GetProperty("releaseTests").GetProperty("passed").GetInt32());
|
|
Assert.Contains("public-package-restore", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
|
|
Assert.Contains("representative-external-nat", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
|
|
Assert.DoesNotContain("actual-godot-process-integration", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
|
|
Assert.DoesNotContain("dedicated-fallback-connection", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
|
|
Assert.Contains("Do not mark #21 passed", guide, StringComparison.Ordinal);
|
|
Assert.Contains("Rendezvous reserves no gameplay", guide, StringComparison.Ordinal);
|
|
Assert.Contains("private-network service name", guide, StringComparison.Ordinal);
|
|
Assert.Contains("local/private-bridge smoke", deploymentGuide, StringComparison.Ordinal);
|
|
Assert.Contains("explicit `rendezvous` host name", deploymentGuide, StringComparison.Ordinal);
|
|
|
|
using JsonDocument composeSettings = JsonDocument.Parse(File.ReadAllText(
|
|
Path.Combine(root, "deploy", "compose", "appsettings.Production.json")));
|
|
JsonElement compose = composeSettings.RootElement;
|
|
Assert.Contains("rendezvous", compose.GetProperty("AllowedHosts").GetString()!.Split(';'));
|
|
JsonElement game = compose.GetProperty("Rendezvous").GetProperty("Provisioning").GetProperty("Games")[0];
|
|
Assert.Contains(2, game.GetProperty("ProtocolVersions").EnumerateArray().Select(static version => version.GetInt32()));
|
|
Assert.Equal("DedicatedEndpointAllowed", game.GetProperty("FallbackPolicy").GetString());
|
|
Assert.DoesNotMatch(ReusableCredential(), guide);
|
|
}
|
|
|
|
[Fact]
|
|
public void UnscoutedPilotEvidenceProvesAnIndependentGameBoundaryAndKeepsExternalGatesOpen()
|
|
{
|
|
string root = FindRepositoryRoot();
|
|
string guide = File.ReadAllText(Path.Combine(root, "docs", "integration", "unscouted-pilot.md"));
|
|
using JsonDocument evidence = JsonDocument.Parse(File.ReadAllText(
|
|
Path.Combine(root, "docs", "evidence", "consumers", "unscouted.json")));
|
|
JsonElement record = evidence.RootElement;
|
|
JsonElement run = record.GetProperty("godotRun");
|
|
JsonElement negative = record.GetProperty("negativePaths");
|
|
|
|
Assert.Equal("checkpoint-pass-with-external-gates", record.GetProperty("result").GetString());
|
|
Assert.Equal("unscouted", record.GetProperty("configuration").GetProperty("gameId").GetString());
|
|
Assert.Equal(["mode", "world", "mods"], record.GetProperty("configuration").GetProperty("metadataKeys")
|
|
.EnumerateArray().Select(static value => value.GetString()));
|
|
Assert.Equal("none", run.GetProperty("rendezvousGameplayPayloadPath").GetString());
|
|
Assert.Equal("unscouted-litenetlib", run.GetProperty("gameplayTransport").GetString());
|
|
Assert.True(run.GetProperty("directGameplay").GetBoolean());
|
|
Assert.True(run.GetProperty("fallbackGameplay").GetBoolean());
|
|
Assert.Equal(2, run.GetProperty("authenticatedSessions").GetInt32());
|
|
Assert.Equal("proven-exact-NotFound", negative.GetProperty("wrongGame").GetString());
|
|
Assert.Equal("proven-exact-NotFound", negative.GetProperty("wrongEnvironment").GetString());
|
|
Assert.Equal(3310, record.GetProperty("verification").GetProperty("consumerDebugTests").GetProperty("passed").GetInt32());
|
|
Assert.Equal(360, record.GetProperty("verification").GetProperty("consumerGdUnitTests").GetProperty("passed").GetInt32());
|
|
Assert.Contains("public-package-restore", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
|
|
Assert.Contains("representative-external-nat", record.GetProperty("openGates").EnumerateArray().Select(static gate => gate.GetString()));
|
|
Assert.Contains("Do not mark #22 passed", guide, StringComparison.Ordinal);
|
|
Assert.Contains("not an Unscouted branch", guide, StringComparison.Ordinal);
|
|
Assert.DoesNotMatch(ReusableCredential(), guide);
|
|
}
|
|
|
|
[GeneratedRegex(@"rv1\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+", RegexOptions.CultureInvariant)]
|
|
private static partial Regex ReusableCredential();
|
|
|
|
[GeneratedRegex(@"(?<method>GET|POST) `?(?<path>/v1/operator/[a-z/-]+)`?", RegexOptions.CultureInvariant)]
|
|
private static partial Regex OperatorRoute();
|
|
|
|
[GeneratedRegex(@"\| `(?<method>POST) (?<path>/v1/operator/[a-z/-]+)` \| `(?<body>\{[^`]+\})` \|", 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.");
|
|
}
|
|
}
|