diff --git a/packages/cultnet-py/tests/test_cultnet.py b/packages/cultnet-py/tests/test_cultnet.py index 5ece34be..0c8bcbf3 100644 --- a/packages/cultnet-py/tests/test_cultnet.py +++ b/packages/cultnet-py/tests/test_cultnet.py @@ -13,6 +13,7 @@ from datetime import UTC, datetime from pathlib import Path from typing import Callable from uuid import uuid4 +import msgpack from cultcache_py import CultCache, define_database_entry_type from cultnet_py.benchmark import run_benchmark from cultnet_py.compare_csharp import ( @@ -1600,25 +1601,13 @@ class CultNetTests(unittest.TestCase): with self.subTest(field=field, bad=bad), self.assertRaisesRegex(ValueError, field): CultNetDatabaseChange.from_wire({**base, field: bad}) - def test_a_two_change_commit_from_the_csharp_server_parses(self) -> None: - # CultNetDatabaseServer.DeliverCommit (CultNetDatabaseServer.cs:791-797) tags every change of a commit with its - # commitId and commitCount = the number of changes delivered, so a swap of two records arrives with commitCount 2. - # Shaped as the C# [MessagePackObject] writes it: every key present, nil where unset. - swap = [ - { - "schemaVersion": "cultnet.database_change_raw.v0", - "messageId": f"change-{index}", - "subscriptionId": "sub-1", - "changeKind": "updated", - "document": None, - "recordKey": key, - "schemaId": "schema-note", - "commitId": "9f1c0f0e4b7a4c1f9d2e", - "commitCount": 2, - } - for index, key in enumerate(("note:a", "note:b")) - ] - changes = [CultNetDatabaseChange.from_wire(message) for message in swap] + def test_a_two_change_commit_written_by_csharp_parses(self) -> None: + # tests/vectors/database-change-commit/two-change-commit.msgpack: the two change messages of one commit, written by the + # C# serializer (CultNetReplicaCommitTests), so a rename or retyping of commitId/commitCount fails here. + vector = Path(__file__).resolve().parents[3] / "tests" / "vectors" / "database-change-commit" / "two-change-commit.msgpack" + messages = list(msgpack.Unpacker(io.BytesIO(vector.read_bytes()), raw=False)) + self.assertEqual(len(messages), 2) + changes = [CultNetDatabaseChange.from_wire(message) for message in messages] self.assertEqual([change.commit_count for change in changes], [2, 2]) self.assertEqual({change.commit_id for change in changes}, {"9f1c0f0e4b7a4c1f9d2e"}) self.assertEqual([change.to_wire()["commitCount"] for change in changes], [2, 2]) diff --git a/tests/GameCult.Networking.Tests/CultNetReplicaCommitTests.cs b/tests/GameCult.Networking.Tests/CultNetReplicaCommitTests.cs index 6059d7ba..b38592ec 100644 --- a/tests/GameCult.Networking.Tests/CultNetReplicaCommitTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetReplicaCommitTests.cs @@ -225,6 +225,39 @@ namespace GameCult.Networking.Tests Assert.That(run.Faults, Has.Count.EqualTo(1)); } + [Test] + public void ATwoChangeCommitIsWrittenAsTheVectorOtherRuntimesDecode() + { + // tests/vectors/database-change-commit/two-change-commit.msgpack: the two change messages of one commit, as the C# + // serializer writes them, concatenated. Written with CULTCACHE_WRITE_VECTORS=1; every other run compares bytes. + var commit = Enumerable.Range(0, 2).Select(index => new CultNetDatabaseChangeRawMessage + { + MessageId = $"change-{index}", + SubscriptionId = "sub-1", + ChangeKind = "removed", + RecordKey = index == 0 ? "note:a" : "note:b", + SchemaId = "schema-note", + CommitId = "9f1c0f0e4b7a4c1f9d2e", + CommitCount = 2 + }); + var bytes = commit.SelectMany(message => CultNetSchemaMessageSerialization.Serialize(message)).ToArray(); + + var directory = TestContext.CurrentContext.TestDirectory; + while (directory != null && !System.IO.Directory.Exists(System.IO.Path.Combine(directory, "tests", "vectors"))) + directory = System.IO.Path.GetDirectoryName(directory); + Assert.That(directory, Is.Not.Null, "tests/vectors is above the test directory"); + var path = System.IO.Path.Combine(directory!, "tests", "vectors", "database-change-commit", "two-change-commit.msgpack"); + if (Environment.GetEnvironmentVariable("CULTCACHE_WRITE_VECTORS") == "1") + { + System.IO.Directory.CreateDirectory(System.IO.Path.GetDirectoryName(path)!); + System.IO.File.WriteAllBytes(path, bytes); + return; + } + + Assert.That(System.IO.File.Exists(path), Is.True, "the vector is committed"); + Assert.That(Convert.ToHexString(bytes), Is.EqualTo(Convert.ToHexString(System.IO.File.ReadAllBytes(path)))); + } + [Test] public void AnOlderPeerReadsTheChangeAndIgnoresTheCommitFields() { diff --git a/tests/vectors/database-change-commit/README.md b/tests/vectors/database-change-commit/README.md new file mode 100644 index 00000000..39a78fa1 --- /dev/null +++ b/tests/vectors/database-change-commit/README.md @@ -0,0 +1,10 @@ +# database-change-commit vectors + +Written only by the C# reference, `ATwoChangeCommitIsWrittenAsTheVectorOtherRuntimesDecode` in +`tests/GameCult.Networking.Tests/CultNetReplicaCommitTests.cs`, with `CULTCACHE_WRITE_VECTORS=1`. Every other run +compares the bytes. + +- `two-change-commit.msgpack`: two `cultnet.database_change_raw.v0` messages (`removed`, `note:a` and `note:b`) + concatenated, each as the C# `[MessagePackObject]` writes it (every key present, nil where unset), both carrying + `commitId` `9f1c0f0e4b7a4c1f9d2e` and `commitCount` 2. Read it as a msgpack stream. Python walks it in + `packages/cultnet-py/tests/test_cultnet.py`. diff --git a/tests/vectors/database-change-commit/two-change-commit.msgpack b/tests/vectors/database-change-commit/two-change-commit.msgpack new file mode 100644 index 00000000..055dc4ae --- /dev/null +++ b/tests/vectors/database-change-commit/two-change-commit.msgpack @@ -0,0 +1 @@ +‰­schemaVersion¾cultnet.database_change_raw.v0©messageId¨change-0®subscriptionId¥sub-1ªchangeKind§removed¨documentÀ©recordKey¦note:a¨schemaId«schema-note¨commitId´9f1c0f0e4b7a4c1f9d2e«commitCount‰­schemaVersion¾cultnet.database_change_raw.v0©messageId¨change-1®subscriptionId¥sub-1ªchangeKind§removed¨documentÀ©recordKey¦note:b¨schemaId«schema-note¨commitId´9f1c0f0e4b7a4c1f9d2e«commitCount \ No newline at end of file