Cover GetByIndex resolving through assignable types

GetByIndexMatchesAssignableTypes: a [CultIndex] on a base member of two
derived documents resolves GetByIndex<Base> 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<T> already filtered
assignable types; no CultCache change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Meta Vens 2026-09-14 13:43:45 +02:00
commit b6ed9e5f83

View file

@ -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<RoutingIndexedBase>("code", "alpha"), Is.InstanceOf<RoutingIndexedLeaf>(),
"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<RoutingIndexedBase>("code", "alpha"),
Throws.InvalidOperationException.With.Message.Contains(leaf.Value).And.Message.Contains(twin.Value));
Assert.That(cache.GetByIndex<RoutingIndexedLeaf>("code", "alpha"), Is.InstanceOf<RoutingIndexedLeaf>());
}
[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
{
}