diff --git a/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs b/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs index acf5eb6c..63747c29 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshStreamingTests.cs @@ -938,6 +938,32 @@ public sealed class CultMeshStreamingTests await act.Should().ThrowAsync(); } + // A database serves only what its shards own, including when a handle reads a same-schema instance of another CLR type + // out of the cache behind it. + [TestCase("tests.mesh_note.v1", true)] + [TestCase("tests.unrelated.v1", false)] + public async Task DocumentHandle_ReadsASameSchemaInstanceOnlyWhenAShardOwnsIt(string ownedSchemaId, bool served) + { + var cache = new CultCache(); + var key = new CultRecordKey("note:one"); + await cache.UpsertAsync(new MeshNoteDocument { Schema = "tests.mesh_note.v1", Text = "cached" }, new CultRecordHandle(key)); + var database = new CultNetDatabase(cache, new CultNetDatabaseOptions + { + RuntimeId = "reader", + Shards = new[] { new CultNetShardDescriptor("notes", "reader", epoch: 1, isPrimary: true, schemaIds: new[] { ownedSchemaId }) } + }); + var handle = CultMesh.Document(database, key, CultMesh.Verse("starbridge", "reader")); + + if (served) + { + (await handle.LatestAsync()).Text.Should().Be("cached"); + } + else + { + await FluentActions.Awaiting(() => handle.LatestAsync()).Should().ThrowAsync(); + } + } + [Test] public async Task DocumentHandle_SubmitsPredictionsThroughCultNetDatabase() { diff --git a/tests/GameCult.Networking.Tests/CultNetJournalTests.cs b/tests/GameCult.Networking.Tests/CultNetJournalTests.cs index d6f9d6d6..8d487bbb 100644 --- a/tests/GameCult.Networking.Tests/CultNetJournalTests.cs +++ b/tests/GameCult.Networking.Tests/CultNetJournalTests.cs @@ -727,7 +727,7 @@ namespace GameCult.Networking.Tests // CultNetErrorMessage carrying the refusal. The handler is reached through its private delegate with an // uninitialized peer, as the R-AM test does; the send then fails on that peer, after the failure was logged. [Test] - public async Task TheServerAnswersARemotePutOfASchemaNoShardOwnsWithAnApplicationRejection() + public void TheServerAnswersARemotePutOfASchemaNoShardOwnsWithAnApplicationRejection() { var (database, cache, _, unowned) = OwnedNothingOfNote(); using var server = new Server(cache, ServerSecurityOptions.Development()); @@ -739,7 +739,8 @@ namespace GameCult.Networking.Tests .GetValue(databaseServer)!; var peer = (CultNetServerPeer)System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject(typeof(CultNetServerPeer)); - try { await handler(UnownedPut(database), peer); } catch (Exception) { } + // The handler answers the peer with the refusal; the uninitialized peer cannot send, which is how the test knows it tried. + Assert.CatchAsync(async () => await handler(UnownedPut(database), peer)); Assert.That(logger.Warnings, Has.Some.Contains("raw put refused").And.Contains(unowned)); Assert.That(logger.Errors, Is.Empty, "a refusal is not a fault");