From e5c4912b6d734a4b0e092da44119a0df460f6c6d Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 7 Sep 2026 00:30:20 +0200 Subject: [PATCH] test(vt): pin the route store's legacy-JSON migration (round 2 step 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds the same migrate-and-delete / counterpart-already-exists coverage for MossTankRouteProfileStore that Settings and Meta already had, completing step 4's "pin with a test per store" requirement. Also corrects an assumption in the route store's own waypoint model: metaf's "pnt" node format is bare x/y/z (three doubles, MetafSerializer.cs:217) with no cell id at all for any waypoint type, not just "jmp" — so the migration test checks EastWest/NorthSouth instead of CellId. Mutation shown to fail: MossTankRouteProfileStore.MigrateLegacyIfNeeded short-circuited to a no-op made RouteStoreMigratesLegacyJsonProfileToAfAndDeletesTheJsonKey fail (LoadCurrent returned false, nothing to load); restored, it passes along with the companion counterpart-exists test. 593 MossTank tests passing (was 591). Co-Authored-By: Claude Fable 5.1 --- .../NavigationTests.cs | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/tests/AcDream.Plugins.MossTank.Tests/NavigationTests.cs b/tests/AcDream.Plugins.MossTank.Tests/NavigationTests.cs index fdac3af75..1c3ebd572 100644 --- a/tests/AcDream.Plugins.MossTank.Tests/NavigationTests.cs +++ b/tests/AcDream.Plugins.MossTank.Tests/NavigationTests.cs @@ -458,6 +458,82 @@ public sealed class NavigationTests Assert.Equal(-3, target.DoorLockpickExcessThreshold); } + /// + /// Reproduces MossTankRouteProfileStore's pre-cutover by-character JSON + /// hash key (its own LegacyProfileKey is private; the format is + /// the migration contract itself, reproduced verbatim here). + /// + private static string LegacyRouteByCharacterKey(string characterName) + { + string identity = "char:" + characterName.Trim().ToUpperInvariant(); + string hash = Convert.ToHexString( + System.Security.Cryptography.SHA256.HashData( + System.Text.Encoding.UTF8.GetBytes(identity))); + return $"profiles/route/{hash}.json"; + } + + [Fact] + public void RouteStoreMigratesLegacyJsonProfileToAfAndDeletesTheJsonKey() + { + var storage = new MemoryStorage(); + string legacyKey = LegacyRouteByCharacterKey("Barris"); + storage.Text[legacyKey] = """ + { + "Mode": 1, + "Waypoints": [ + { "Type": 0, "EastWest": 5.0, "NorthSouth": 6.0 } + ] + } + """; + + var store = new MossTankRouteProfileStore(new FakeHost(new FakeAutomation(), storage)); + Assert.True(store.BindCharacter("Barris")); + var target = new NavigationSettings(); + Assert.True(store.LoadCurrent(target, MetafSerializer.NoOpSpells.Instance)); + + Assert.False(storage.Text.ContainsKey(legacyKey)); + Assert.Equal(RouteMode.Linear, target.Mode); + RouteWaypoint waypoint = Assert.Single(target.Waypoints); + // metaf's "pnt" node format is bare x/y/z (three doubles) — no cell + // id at all, for any waypoint type — so only the coordinates + // round-trip. + Assert.Equal(5.0d, waypoint.Position.EastWest, precision: 3); + Assert.Equal(6.0d, waypoint.Position.NorthSouth, precision: 3); + + // Idempotent second run: nothing left to migrate. + var reopened = new MossTankRouteProfileStore(new FakeHost(new FakeAutomation(), storage)); + Assert.True(reopened.BindCharacter("Barris")); + var reloaded = new NavigationSettings(); + Assert.True(reopened.LoadCurrent(reloaded, MetafSerializer.NoOpSpells.Instance)); + Assert.Single(reloaded.Waypoints); + } + + [Fact] + public void RouteStoreLeavesLegacyJsonUntouchedWhenAfCounterpartExists() + { + var storage = new MemoryStorage(); + string legacyKey = LegacyRouteByCharacterKey("Barris"); + storage.Text[legacyKey] = """{ "Mode": 1, "Waypoints": [] }"""; + string realKey = "nav_" + VtankProfileDirectory.AutoCharacterFileName( + "Barris", string.Empty, "af"); + var real = new NavigationSettings { Mode = RouteMode.Circular }; + real.Waypoints.Add(new RouteWaypoint + { + Type = RouteWaypointType.Point, + Position = new PluginNavigationPosition(0x00010001u, 1d, 2d, 0d, 0f, true), + }); + storage.Text[realKey] = MetafSerializer.SaveNav(real); + + var store = new MossTankRouteProfileStore(new FakeHost(new FakeAutomation(), storage)); + Assert.True(store.BindCharacter("Barris")); + var target = new NavigationSettings(); + Assert.True(store.LoadCurrent(target, MetafSerializer.NoOpSpells.Instance)); + + Assert.True(storage.Text.ContainsKey(legacyKey)); + Assert.Equal(RouteMode.Circular, target.Mode); + Assert.Single(target.Waypoints); + } + [Fact] public void FollowModeRouteRoundTripsTheFollowTargetThroughAf() { @@ -619,6 +695,7 @@ public sealed class NavigationTests private sealed class MemoryStorage : IPluginStorage { private readonly Dictionary _text = new(StringComparer.Ordinal); + public Dictionary Text => _text; public bool IsAvailable => true; public string? ReadText(string key) => _text.TryGetValue(key, out string? value) ? value : null;