Bifrost/tests/Bifrost.Web.Tests/UnixSocketDeskDoorRequiredKeysTests.cs
Heimdall 2d2fdeb0e2 Desk door: the wire walker's Closed is the only owner of required keys
JsonRequired on DeskAnswerDocument.Answerer and ForkRecord.AnswererCount was a second owner of what Closed already
decides at the boundary; nothing the typed vector replays needed is lost (they now go through the door). Every key of
the list, entry, record and answer documents, taken from a real CultLib entry, is removed and nulled: a missing key, or a
nil where the contract has none, is a door failure. First red: the test that the typed records declare no required keys.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-11 19:20:53 +00:00

96 lines
3.5 KiB
C#

using System.Text.Json.Nodes;
using Bifrost.Web.Features.Desk;
using Bifrost.Web.Tests.Support;
namespace Bifrost.Web.Tests;
// The wire walker's closed maps (Closed in UnixSocketDeskDoor) are the single owner of "every contract key present with
// its type": the typed records carry no JsonRequired of their own. Each key of each contract document, taken from a real
// CultLib entry, is removed and, where the contract does not allow nil, nulled: every one is a door failure.
public sealed class UnixSocketDeskDoorRequiredKeysTests
{
private static readonly HashSet<string> Nullable = ["next_cursor", "counted_answer_id", "of", "retracts", "code", "choice", "note", "answerer"];
private static JsonObject Level(JsonObject page, string level) => level switch
{
"list" => page,
"entry" => page["forks"]![0]!.AsObject(),
"record" => page["forks"]![0]!["record"]!.AsObject(),
"answer" => page["forks"]![0]!["record"]!["answers"]![0]!.AsObject(),
_ => throw new ArgumentOutOfRangeException(nameof(level)),
};
public static TheoryData<string, string> Keys()
{
var page = DeskServe.PageOf(DeskServe.Entry("entry-open-counted"));
var data = new TheoryData<string, string>();
foreach (var level in new[] { "list", "entry", "record", "answer" })
{
foreach (var key in Level(page, level).Select(p => p.Key))
{
data.Add(level, key);
}
}
return data;
}
private static JsonObject Page() => DeskServe.PageOf(DeskServe.Entry("entry-open-counted"));
[Theory]
[MemberData(nameof(Keys))]
public async Task A_missing_required_key_is_a_door_failure(string level, string key)
{
var page = Page();
Level(page, level).Remove(key);
await Assert.ThrowsAsync<DeskDoorException>(() => DeskServe.ListPayloadAsync(DeskServe.Forks(DeskServe.FromJson(page))));
}
[Theory]
[MemberData(nameof(Keys))]
public async Task A_nil_where_the_contract_has_no_nil_is_a_door_failure(string level, string key)
{
var page = Page();
Level(page, level)[key] = null;
var failing = !Nullable.Contains(key);
var served = DeskServe.ListPayloadAsync(DeskServe.Forks(DeskServe.FromJson(page)));
if (failing)
{
await Assert.ThrowsAsync<DeskDoorException>(() => served);
}
else
{
Assert.IsType<DeskListOutcome.Listed>(await served);
}
}
[Theory]
[InlineData("answerer")]
[InlineData("answerer_count_is_not_an_answer_key")]
public async Task A_stored_answer_in_an_answered_reply_needs_the_same_keys(string key)
{
var answer = DeskServe.Entry("entry-open-counted")["record"]!["answers"]![0]!.AsObject();
if (answer.ContainsKey(key))
{
answer.Remove(key);
}
else
{
answer[key] = 1;
}
await Assert.ThrowsAsync<DeskDoorException>(() => DeskServe.AnswerPayloadAsync(DeskServe.Answered(DeskServe.FromJson(answer), "n1")));
}
[Fact]
public void The_typed_records_declare_no_required_keys_of_their_own()
{
var typed = new[] { typeof(DeskAnswerDocument), typeof(ForkRecord), typeof(ForkEntry), typeof(ForkList), typeof(AnswerResult) };
var required = typed.SelectMany(t => t.GetProperties()).Where(p => p.GetCustomAttributes(typeof(System.Text.Json.Serialization.JsonRequiredAttribute), true).Length > 0);
Assert.Empty(required.Select(p => p.DeclaringType!.Name + "." + p.Name));
}
}