From 48cf3b332368bf2d83fbe9874a6770e21ca7b966 Mon Sep 17 00:00:00 2001 From: Libby Date: Sun, 8 Nov 2026 16:45:29 +0000 Subject: [PATCH] C#: tests kill the replay-details and unreliable non-write mutants Co-Authored-By: Claude Sonnet 5.5 --- .../CultNetWriteReceiptLoopbackTests.cs | 3 ++ .../CultNetWriteReceiptServerTests.cs | 34 +++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/tests/GameCult.Networking.Tests/CultNetWriteReceiptLoopbackTests.cs b/tests/GameCult.Networking.Tests/CultNetWriteReceiptLoopbackTests.cs index 19180459..fb07ee58 100644 --- a/tests/GameCult.Networking.Tests/CultNetWriteReceiptLoopbackTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetWriteReceiptLoopbackTests.cs @@ -231,6 +231,9 @@ namespace GameCult.Networking.Tests receipt.SendUnreliable(HelloOf(BothFrames)); // not a write: answered with nothing on the unreliable channel receipt.Send(running.PutOf("r1", KeyA, "one")); Assert.That(receipt.Next().MessageId, Is.EqualTo("r1"), "nothing else was sent ahead of the receipt"); + receipt.Send(running.PutOf("r2", KeyA, "two")); + Assert.That(receipt.Next().MessageId, Is.EqualTo("r2")); + receipt.ExpectNothingMore("the unreliable hello was answered with nothing"); using var legacy = running.Connect(); legacy.Send(HelloOf()); diff --git a/tests/GameCult.Networking.Tests/CultNetWriteReceiptServerTests.cs b/tests/GameCult.Networking.Tests/CultNetWriteReceiptServerTests.cs index 7c6537a3..b50ac5de 100644 --- a/tests/GameCult.Networking.Tests/CultNetWriteReceiptServerTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetWriteReceiptServerTests.cs @@ -1171,5 +1171,39 @@ namespace GameCult.Networking.Tests GC.KeepAlive(session); Assert.That(growth, Is.LessThan(8L * 1024 * 1024)); } + + // A remembered refusal is replayed with the details its code carries: the schema id of an unowned write, no details + // at all for the routing refusal. + [Test] + public void AReplayedRefusalCarriesTheDetailsOfItsCodeAndNoOthers() + { + var session = new CultNetWriteReceiptSession(); + var sent = new List(); + CultNetReceiptedWrite Write(string id) => new(session, sent.Add, "put", id, "schema.v1", "note:1", [1]); + + var unowned = Write("u1"); + Assert.That(unowned.Begin(), Is.True); + unowned.Refused(CultNetErrorMessage.ForUnownedSchema(new CultNetUnownedSchemaException("schema.v1", new CultRecordKey("note:1")))); + Assert.That(Write("u1").Begin(), Is.False); + + var routed = Write("g1"); + Assert.That(routed.Begin(), Is.True); + routed.Refused(new CultNetErrorMessage + { + Error = CultNetDatabaseServer.RoutingRefusalText, + RoutingHint = new CultNetShardRoutingHint { Reason = "not_primary" } + }); + Assert.That(Write("g1").Begin(), Is.False); + + var unownedAgain = (CultNetErrorV1Message)sent[1]; + Assert.That(unownedAgain.Code, Is.EqualTo("unowned_schema")); + Assert.That(unownedAgain.Details!.Field, Is.EqualTo("schemaId")); + Assert.That(unownedAgain.Details.Value, Is.EqualTo("schema.v1")); + Assert.That(unownedAgain.Error, Is.EqualTo(((CultNetErrorV1Message)sent[0]).Error)); + var routedAgain = (CultNetErrorV1Message)sent[3]; + Assert.That(routedAgain.Details, Is.Null); + Assert.That(routedAgain.RoutingHint!.Reason, Is.EqualTo("not_primary")); + Assert.That(routedAgain.Error, Is.EqualTo(CultNetDatabaseServer.RoutingRefusalText)); + } } }