From b6ed9e5f83f06cf3323f2ad18a70c593b072e6f5 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 14 Sep 2026 13:43:45 +0200 Subject: [PATCH] Cover GetByIndex resolving through assignable types GetByIndexMatchesAssignableTypes: a [CultIndex] on a base member of two derived documents resolves GetByIndex to the derived record, throws naming both keys once the value is ambiguous across the two types, and still resolves by the concrete type. GetByIndex already filtered assignable types; no CultCache change. Co-Authored-By: Claude Opus 5 --- .../StoreRoutingTests.cs | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/tests/GameCult.Caching.Tests/StoreRoutingTests.cs b/tests/GameCult.Caching.Tests/StoreRoutingTests.cs index 9a65dc89..fc99f01f 100644 --- a/tests/GameCult.Caching.Tests/StoreRoutingTests.cs +++ b/tests/GameCult.Caching.Tests/StoreRoutingTests.cs @@ -410,6 +410,22 @@ namespace GameCult.Caching.Tests Assert.That(reader.AllEntries, Is.Empty); } + [Test] + public async Task GetByIndexMatchesAssignableTypes() + { + using var cache = new CultCache(CultDocumentRegistry.ForTypes(new[] { typeof(RoutingIndexedLeaf), typeof(RoutingIndexedTwin) })); + var leaf = await cache.UpsertAsync(typeof(RoutingIndexedLeaf), new RoutingIndexedLeaf { Code = "alpha" }); + + Assert.That(cache.GetByIndex("code", "alpha"), Is.InstanceOf(), + "an index declared on a base member resolves through the base type"); + + var twin = await cache.UpsertAsync(typeof(RoutingIndexedTwin), new RoutingIndexedTwin { Code = "alpha" }); + + Assert.That(() => cache.GetByIndex("code", "alpha"), + Throws.InvalidOperationException.With.Message.Contains(leaf.Value).And.Message.Contains(twin.Value)); + Assert.That(cache.GetByIndex("code", "alpha"), Is.InstanceOf()); + } + [Test] public async Task AmbiguousAssignableLookupThrows() { @@ -768,6 +784,25 @@ namespace GameCult.Caching.Tests public string Name = string.Empty; } + internal abstract class RoutingIndexedBase + { + [Key(0)] + [CultIndex("code")] + public string Code = string.Empty; + } + + [CultDocument("tests.routing_indexed_leaf", "tests.routing_indexed_leaf.v1")] + [MessagePackObject(AllowPrivate = true)] + internal sealed class RoutingIndexedLeaf : RoutingIndexedBase + { + } + + [CultDocument("tests.routing_indexed_twin", "tests.routing_indexed_twin.v1")] + [MessagePackObject(AllowPrivate = true)] + internal sealed class RoutingIndexedTwin : RoutingIndexedBase + { + } + internal abstract class RoutingGlobalBase { }