Bifrost/tests/Bifrost.Web.Tests/ProviderAdvertisementTests.cs
2026-08-22 06:16:39 +02:00

194 lines
9.4 KiB
C#

using System.Diagnostics;
using System.Text.Json;
namespace Bifrost.Web.Tests;
public sealed class ProviderAdvertisementTests
{
[Fact]
public async Task Provider_advertisement_names_current_bridge_and_patron_contracts()
{
var result = await RunNodeAsync([
"tools/provider-advertisement.mjs",
"print",
"--generated-at",
"2026-06-22T00:00:00.000Z",
]);
Assert.True(result.ExitCode == 0, $"stdout:{Environment.NewLine}{result.Stdout}{Environment.NewLine}stderr:{Environment.NewLine}{result.Stderr}");
using var payload = JsonDocument.Parse(result.Stdout);
var root = payload.RootElement;
AssertEndpoint(root, "crossing-receipts", "bifrost.crossing_receipt.v1", "bridge");
AssertEndpoint(root, "patron-support-intake", "bifrost.patron_support_event.v0", "patronage");
AssertEndpoint(root, "github-webhooks", "bifrost.work_item.v0", "github");
Assert.Equal("asgard.yggdrasil.bifrost", root.GetProperty("locatedService").GetString());
AssertSchemaPurpose(root, "bifrost.crossing_receipt.v1", "canonical Bifrost-owned public crossing receipt");
AssertSchemaPurpose(root, "bifrost.patron_support_event.v0", "current hosted Heimdall-signed patron support fact consumed by Bifrost");
AssertWitness(root, ".bifrost/bridge-receipts.cc", "bifrost.crossing_receipt.v1", "current");
AssertBoundaryCommand(root, "bridge", "open GitHub draft PRs and PR comments through Bifrost gate");
AssertBoundaryCommand(root, "bridge", "own canonical bifrost.crossing_receipt.v1 documents for every crossing attempt");
AssertBoundaryCommand(root, "bridge", "post Discord messages and DMs through Bifrost gate with Heimdall-linked actor capability");
AssertBoundaryCommand(root, "bridge", "record receipt-only future-surface requests with named-surface Heimdall capability matching before named actuators exist");
AssertBoundaryCommand(root, "bridge", "post Persona-flaired Reddit organizing threads");
AssertBoundaryForbiddenAuthority(root, "bridge", "surface-specific receipts cannot decide crossing completion");
AssertBoundaryCommand(root, "patron", "consume Heimdall-signed Patreon and PayPal support facts");
AssertBoundaryForbiddenAuthority(root, "patron", "does not store Patreon or PayPal provider tokens");
}
[Fact]
public async Task Interface_binding_exposes_current_agent_bridge_capabilities()
{
var result = await RunNodeAsync([
"tools/provider-advertisement.mjs",
"print-binding",
"--generated-at",
"2026-06-22T00:00:00.000Z",
]);
Assert.True(result.ExitCode == 0, $"stdout:{Environment.NewLine}{result.Stdout}{Environment.NewLine}stderr:{Environment.NewLine}{result.Stderr}");
using var payload = JsonDocument.Parse(result.Stdout);
var root = payload.RootElement;
var provider = root.GetProperty("provider");
AssertArrayContains(provider.GetProperty("capabilities"), "github-bridge");
AssertArrayContains(provider.GetProperty("capabilities"), "github-work-sync");
AssertArrayContains(provider.GetProperty("capabilities"), "discord-bridge");
AssertArrayContains(provider.GetProperty("capabilities"), "reddit-bridge");
AssertArrayContains(provider.GetProperty("capabilities"), "future-surface-bridge");
AssertArrayContains(provider.GetProperty("capabilities"), "heimdall-patron-support-intake");
var stats = root.GetProperty("surface").GetProperty("root").GetProperty("props");
Assert.Equal("2026-06-22T00:00:00.000Z", stats.GetProperty("generatedAt").GetString());
var bridgeMetric = root
.GetProperty("surface")
.GetProperty("root")
.GetProperty("children")
.EnumerateArray()
.SelectMany(panel => panel.GetProperty("children").EnumerateArray())
.Single(node => node.GetProperty("id").GetString() == "metric-bridge");
var bridgeLine = bridgeMetric.GetProperty("props").GetProperty("value").GetString();
Assert.Contains("GitHub live", bridgeLine, StringComparison.Ordinal);
Assert.Contains("Discord prepared", bridgeLine, StringComparison.Ordinal);
Assert.Contains("Reddit prepared", bridgeLine, StringComparison.Ordinal);
Assert.Contains("Other prepared", bridgeLine, StringComparison.Ordinal);
Assert.Contains("Patron live", bridgeLine, StringComparison.Ordinal);
Assert.Equal("warn", bridgeMetric.GetProperty("props").GetProperty("tone").GetString());
var readinessRows = root
.GetProperty("surface")
.GetProperty("root")
.GetProperty("children")
.EnumerateArray()
.SelectMany(panel => panel.GetProperty("children").EnumerateArray())
.Single(node => node.GetProperty("id").GetString() == "list-bridge-readiness")
.GetProperty("children")
.EnumerateArray()
.Select(node => node.GetProperty("props").GetProperty("text").GetString())
.ToArray();
Assert.Contains(readinessRows, row => row is not null && row.Contains("reddit: prepared", StringComparison.Ordinal));
Assert.Contains(readinessRows, row => row is not null && row.Contains("patron: live", StringComparison.Ordinal) && row.Contains("Bifrost stores no provider tokens", StringComparison.Ordinal));
var receiptMetric = root
.GetProperty("surface")
.GetProperty("root")
.GetProperty("children")
.EnumerateArray()
.SelectMany(panel => panel.GetProperty("children").EnumerateArray())
.Single(node => node.GetProperty("id").GetString() == "metric-crossing-receipts");
Assert.Contains("total", receiptMetric.GetProperty("props").GetProperty("value").GetString(), StringComparison.Ordinal);
}
private static void AssertEndpoint(JsonElement root, string id, string schemaId, string lowering)
{
var endpoint = root.GetProperty("endpoints")
.EnumerateArray()
.Single(item => item.GetProperty("id").GetString() == id);
Assert.Equal(schemaId, endpoint.GetProperty("schemaId").GetString());
AssertArrayContains(endpoint.GetProperty("lowerings"), lowering);
}
private static void AssertSchemaPurpose(JsonElement root, string id, string purpose)
{
var schema = root.GetProperty("schemas")
.EnumerateArray()
.Single(item => item.GetProperty("id").GetString() == id);
Assert.Equal(purpose, schema.GetProperty("purpose").GetString());
}
private static void AssertWitness(JsonElement root, string path, string schemaId, string status)
{
var witness = root.GetProperty("witnesses")
.EnumerateArray()
.Single(item => item.GetProperty("path").GetString() == path);
Assert.Equal(schemaId, witness.GetProperty("schemaIds").GetString());
Assert.Equal(status, witness.GetProperty("status").GetString());
}
private static void AssertBoundaryCommand(JsonElement root, string area, string command)
{
var boundary = Boundary(root, area);
AssertArrayContains(boundary.GetProperty("commands"), command);
}
private static void AssertBoundaryForbiddenAuthority(JsonElement root, string area, string forbiddenAuthority)
{
var boundary = Boundary(root, area);
AssertArrayContains(boundary.GetProperty("forbiddenAuthority"), forbiddenAuthority);
}
private static JsonElement Boundary(JsonElement root, string area)
=> root.GetProperty("commandBoundaries")
.EnumerateArray()
.Single(item => item.GetProperty("area").GetString() == area);
private static void AssertArrayContains(JsonElement array, string expected)
{
Assert.Contains(array.EnumerateArray(), item => item.GetString() == expected);
}
private static string RepoRoot =>
Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", ".."));
private static async Task<ProcessResult> RunNodeAsync(string[] args)
{
using var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "node",
WorkingDirectory = RepoRoot,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
}
};
foreach (var arg in args)
{
process.StartInfo.ArgumentList.Add(arg);
}
process.StartInfo.Environment["BIFROST_BRIDGE_BASE_URL"] = string.Empty;
process.StartInfo.Environment["BIFROST_BRIDGE_TOKEN"] = string.Empty;
process.StartInfo.Environment["BIFROST_SKIP_LOCAL_ENV"] = "true";
process.StartInfo.Environment["BIFROST_REDDIT_CLIENT_ID"] = string.Empty;
process.StartInfo.Environment["BIFROST_REDDIT_REFRESH_TOKEN"] = string.Empty;
process.StartInfo.Environment["BIFROST_ROOT_VERSE"] = "asgard";
process.StartInfo.Environment["BIFROST_MACHINE_ID"] = "yggdrasil";
process.Start();
var stdout = await process.StandardOutput.ReadToEndAsync();
var stderr = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
return new ProcessResult(process.ExitCode, stdout, stderr);
}
private sealed record ProcessResult(int ExitCode, string Stdout, string Stderr);
}