CultLib/tests/GameCult.Mesh.Tests/CultMeshVariantRefusalTests.cs
Meta Vens 436eed73e4 Store readability: one typed refusal, one full reader, one store shape
CultStoreUnreadableException (a NotSupportedException) is the one refusal for a store file this runtime cannot read, thrown
by the single-file reader, the rewriting paths and CultMesh's reads, with the cause as the inner exception. The reader is exactly
one array of three slots (or an empty array): a header alone and an extra top-level slot are refused instead of read as empty or
skipped. CultMesh's older-catalog fallback is the same reader with the older entry reader, so it also requires one array.

Vectors: bad-body, header-only, missing-schema, variant-slot, extra-top-slot in the readability set, and a legacy-catalog store
with and without bytes after it for CultMesh. The persistence contract says what is true.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-30 04:06:44 +02:00

71 lines
3.1 KiB
C#

using System;
using System.IO;
using FluentAssertions;
using GameCult.Caching;
using NUnit.Framework;
#nullable enable
namespace GameCult.Mesh.Tests;
// Shared refusal vectors: tests/vectors/document-variants-c0, read by every runtime's tests.
public sealed class CultMeshVariantRefusalTests
{
private const string ItemSchemaId = "sha256:88d3fdf0a927acf3b163940d8f8c7fe62b3316542ce771a67ec8bc038f594788";
private static string VectorPath(string name)
{
for (var dir = new DirectoryInfo(TestContext.CurrentContext.TestDirectory); dir != null; dir = dir.Parent)
{
var candidate = Path.Combine(dir.FullName, "tests", "vectors", "document-variants-c0", name);
if (File.Exists(candidate)) return candidate;
}
throw new FileNotFoundException($"Shared vector {name} not found above {TestContext.CurrentContext.TestDirectory}.");
}
private static CultStoreUnreadableException Refusal(string vector) =>
Assert.Throws<CultStoreUnreadableException>(() =>
CultMesh.ReadSingleFileDocumentPayload(VectorPath(vector), new CultRecordKey("item:bellows"), ItemSchemaId))!;
[Test]
public void ReadSingleFileDocument_RefusesUnknownHeaderByName() =>
Refusal("unknown-header.msgpack").Message.Should().Contain("cultcache.store.v9");
[Test]
public void ReadSingleFileDocument_RefusesExtraRecordSlotNamingTheRecord()
{
var message = Refusal("extra-slot-full-payload.msgpack").Message;
message.Should().Contain("item:anvil").And.Contain(ItemSchemaId);
}
[Test]
public void ReadSingleFileDocument_RefusesExtraRecordSlotInTheLegacyCatalogLayoutToo()
{
var message = Refusal("legacy-catalog-extra-slot.msgpack").Message;
message.Should().Contain("item:anvil").And.Contain(ItemSchemaId);
}
// The older catalog layout is read by the same store reader, so it is held to one array too: the same store with bytes after it
// is refused, and only the bytes make the difference.
[Test]
public void ReadSingleFileDocument_ReadsTheLegacyCatalogLayoutButNotWithBytesAfterIt()
{
CultMesh.ReadSingleFileDocumentPayload(VectorPath("legacy-catalog-plain.msgpack"), new CultRecordKey("item:bellows"), ItemSchemaId)
.Should().NotBeEmpty();
Refusal("legacy-catalog-trailing.msgpack").Message.Should().Contain("after its MessagePack array");
}
// Q6: CultMesh does not resolve variants. It refuses a variant key naming it and its base, and still reads the plain
// records of the same v2 store.
[Test]
public void ReadSingleFileDocument_RefusesAVariantKeyByNameButReadsItsPlainNeighbours()
{
var refusal = Assert.Throws<NotSupportedException>(() =>
CultMesh.ReadSingleFileDocumentPayload(VectorPath("variant-v2.msgpack"), new CultRecordKey("item:anvil-big"), ItemSchemaId))!;
refusal.Message.Should().Contain("item:anvil-big").And.Contain("item:anvil'");
CultMesh.ReadSingleFileDocumentPayload(VectorPath("variant-v2.msgpack"), new CultRecordKey("item:bellows"), ItemSchemaId)
.Should().NotBeEmpty();
}
}