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>
57 lines
2.3 KiB
C#
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);
|
|
}
|
|
}
|