Bifrost/tests/Bifrost.Web.Tests/Support/DeskReplay.cs

75 lines
3.1 KiB
C#

using System.Text.Json;
using System.Text.Json.Nodes;
using Bifrost.Web.Features.Desk;
namespace Bifrost.Web.Tests.Support;
// Shared plumbing for replaying CultLib's transport vectors through HTTP.
public static class DeskReplay
{
public const string ChatFork = "gamecult-chat:question:slot-mark-consent";
private static JsonNode BaseCard => DeskVectors.Root["scenarios"]![0]!["card"]!;
public static ForkCard Card(string forkId, string title = "Slot mark consent")
{
var card = JsonNode.Parse(BaseCard.ToJsonString())!.AsObject();
card["fork_id"] = forkId;
card["title"] = title;
return card.Deserialize<ForkCard>(DeskJson.Options)!;
}
public static ForkEntry Entry(ForkCard card) => new(
"gamecult.fork.entry.v0",
card,
new ForkRecord("gamecult.fork.record.v0", card.ForkId, card.OptionsHash, null, []));
// Every fork the vectors say each Mind holds, on that Mind's socket.
public static void HoldVectorForks(FakeDeskDoor door)
{
var forks = DeskVectors.Root["transport"]!["mindRoutes"]!["forks"]!.AsObject();
foreach (var (mind, ids) in forks)
{
foreach (var id in ids!.AsArray())
{
door.Forks[(DeskWebApplicationFactory.SocketOf(mind), (string)id!)] = Entry(Card((string)id!));
}
}
}
public static byte[] AnswerBody(string forkId, string requestId = "00000001-0000-4000-8000-000000000001")
{
var body = JsonNode.Parse(DeskVectors.Root["scenarios"]![0]!["steps"]![0]!["body"]!.ToJsonString())!.AsObject();
body["fork_id"] = forkId;
body["request_id"] = requestId;
return DeskVectors.Bytes(body);
}
// The raw segment as it would travel in one path segment: a literal '/' cannot, so it is sent as %2F.
public static string SegmentOnWire(string rawMind) => rawMind.Replace("/", "%2F", StringComparison.Ordinal);
public static string Target(string route, string rawMind, string? forkId) => route switch
{
"answer" => $"/api/desk/{SegmentOnWire(rawMind)}/answers",
"list" => $"/api/desk/{SegmentOnWire(rawMind)}/forks",
"read" => $"/api/desk/{SegmentOnWire(rawMind)}/forks/{Uri.EscapeDataString(forkId!)}",
_ => throw new ArgumentOutOfRangeException(nameof(route)),
};
// Seeds the account the vector token stands for and returns the header value (null for no token).
public static async Task<string?> TokenAsync(DeskWebApplicationFactory factory, JsonNode? token)
{
if (token is null)
{
return null;
}
var active = (bool)token["active"]!;
var subject = active ? DeskWebApplicationFactory.Alice : DeskWebApplicationFactory.Member;
await factory.EnsureAccountAsync(subject, active);
return factory.Token(subject, (string)token["audience"]!, token["scopes"]!.AsArray().Select(s => (string)s!).ToArray());
}
public static bool IsEdgeRefusal(string code) =>
code is "unauthenticated" or "insufficient_scope" or "forbidden" or "body_forbidden_field" or "body_unknown_field" or "body_invalid";
}