acdream/tests/AcDream.Plugins.MossTank.Tests/MossTankProfileStoreSideCarTests.cs
Erik b6d642c9b7 fix(vt): round 3 item 1 — drop-in .usd EnableLooting no longer clobbered
MossTankProfileStore's SideCarDocument.InventoryEnableLooting duplicated
the real "EnableLooting" .usd Settings row, and the side-car's own Apply()
ran AFTER the .usd was applied in LoadCurrent — so a drop-in profile with
looting on loaded with it silently off, clobbered by whatever the side-car
happened to hold (default false for a profile with no side-car at all).

Deleted the duplicate field; settings.Inventory.Loot.Enabled is now owned
exclusively by the .usd row. Added a reflection test proving no SideCarDocument
field (typed or with its Combat/Buff/Vitals/Inventory group prefix stripped)
collides with a VtankOptionCatalog name, and a drop-in regression test:
a real .usd fixture with EnableLooting=True and NO side-car now loads with
panel.LootEnabled == true.

Mutation: reverted MossTankProfileStore.cs to HEAD (keeping only the new
tests) and ran both new tests — SideCarDocumentHasNoFieldNamedForARealVtankSetting
failed with `["InventoryEnableLooting"]`, DropInUsdWithLootingEnabledAndNoSideCarLoadsLootingEnabled
failed with Expected True/Actual False — confirming both tests exercise the bug
before the fix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-07 01:03:47 +02:00

57 lines
2.3 KiB
C#

using System.Reflection;
namespace AcDream.Plugins.MossTank.Tests;
/// <summary>
/// Round 3 item 1: proves <c>MossTankProfileStore.SideCarDocument</c> (the
/// private JSON side-car for state that has no VTank Settings-table name at
/// all) carries no field that ALSO has a real <c>.usd</c> catalog name —
/// any such overlap is a live duplication risk exactly like the
/// <c>InventoryEnableLooting</c>/<c>EnableLooting</c> bug this round fixed
/// (the side-car's own copy is always applied AFTER the <c>.usd</c> row, so
/// a stale or default side-car value silently clobbers whatever the real
/// profile file says).
/// </summary>
public sealed class MossTankProfileStoreSideCarTests
{
// The side-car's own property names are grouped by settings section
// (Combat*/Buff*/Vitals*/Inventory*) purely for readability — VTank's
// catalog names carry no such prefix, so each candidate is checked both
// as typed and with its known group prefix stripped.
private static readonly string[] GroupPrefixes = ["Combat", "Buff", "Vitals", "Inventory"];
[Fact]
public void SideCarDocumentHasNoFieldNamedForARealVtankSetting()
{
Type sideCar = typeof(MossTankProfileStore).GetNestedType(
"SideCarDocument", BindingFlags.NonPublic)
?? throw new InvalidOperationException(
"MossTankProfileStore.SideCarDocument was not found by reflection.");
var catalogNames = new HashSet<string>(
VtankOptionCatalog.Names, StringComparer.OrdinalIgnoreCase);
var overlaps = new List<string>();
foreach (PropertyInfo property in sideCar.GetProperties(
BindingFlags.Public | BindingFlags.Instance))
{
string name = property.Name;
if (catalogNames.Contains(name))
{
overlaps.Add(name);
continue;
}
foreach (string prefix in GroupPrefixes)
{
if (name.Length > prefix.Length
&& name.StartsWith(prefix, StringComparison.Ordinal)
&& catalogNames.Contains(name[prefix.Length..]))
{
overlaps.Add(name);
break;
}
}
}
Assert.Empty(overlaps);
}
}