diff --git a/dotnet/EcencyApi.Tests/WalletActionsTests.cs b/dotnet/EcencyApi.Tests/WalletActionsTests.cs new file mode 100644 index 00000000..dee23509 --- /dev/null +++ b/dotnet/EcencyApi.Tests/WalletActionsTests.cs @@ -0,0 +1,39 @@ +using System.Text.Json.Nodes; +using EcencyApi.Handlers; +using Xunit; + +namespace EcencyApi.Tests; + +/// +/// The token action ids are a client-facing contract: web and mobile map them to +/// concrete wallet operations, and an id neither client recognizes either renders +/// nothing (web drops unknown ids) or renders an unlabeled control (mobile passes +/// them through). Pin the ids and their order. +/// +public class WalletActionsTests +{ + private static string[] Ids(JsonArray actions) => + actions.Select(a => a!["id"]!.GetValue()).ToArray(); + + [Fact] + public void HpActions_AreTheChainOperationNamesInRenderOrder() + { + Assert.Equal( + new[] { "delegate_vesting_shares", "withdraw_vesting", "set_withdraw_vesting_route" }, + Ids(WalletApi.HpActions())); + } + + [Fact] + public void HiveActions_OnlyOfferSavingsWithdrawalWhenSavingsExist() + { + Assert.DoesNotContain("transfer_from_savings", Ids(WalletApi.BuildHiveActions(0))); + Assert.Contains("transfer_from_savings", Ids(WalletApi.BuildHiveActions(1.5))); + } + + [Fact] + public void HbdActions_OnlyOfferSavingsWithdrawalWhenSavingsExist() + { + Assert.DoesNotContain("transfer_from_savings", Ids(WalletApi.BuildHbdActions(0))); + Assert.Contains("transfer_from_savings", Ids(WalletApi.BuildHbdActions(1.5))); + } +} diff --git a/dotnet/EcencyApi/Handlers/WalletApi.Market.cs b/dotnet/EcencyApi/Handlers/WalletApi.Market.cs index 06e8c58a..1cd00e52 100644 --- a/dotnet/EcencyApi/Handlers/WalletApi.Market.cs +++ b/dotnet/EcencyApi/Handlers/WalletApi.Market.cs @@ -32,7 +32,10 @@ internal static JsonArray BuildHiveActions(double savings) return Actions(ids.ToArray()); } - internal static JsonArray HpActions() => Actions("delegate_vesting_shares", "withdraw_vesting"); + // set_withdraw_vesting_route lets clients link straight to the withdraw routes UI. + // Deliberately added after the original two ids: clients render actions in order. + internal static JsonArray HpActions() => + Actions("delegate_vesting_shares", "withdraw_vesting", "set_withdraw_vesting_route"); internal static JsonArray BuildHbdActions(double savings) { diff --git a/dotnet/parity/driver.py b/dotnet/parity/driver.py index 606be668..ed367eac 100644 --- a/dotnet/parity/driver.py +++ b/dotnet/parity/driver.py @@ -230,6 +230,15 @@ def norm_body(text): "Same request-delete rerouting as ::min.", } +# Deliberately NOT listed above: /wallet-api/portfolio-v2::pop, whose HP action list +# gained set_withdraw_vesting_route (the reference build emits only +# delegate_vesting_shares and withdraw_vesting). A KNOWN_DIVERGENCES entry skips the +# case entirely — status and content-type included — which would blind this large +# aggregation endpoint to unrelated regressions. Its body is already exempt for a +# better reason: balances and APR move every block, so the run-vs-run comparison puts +# it in `loose`, which still checks status and content-type. Add an entry here only +# for a divergence that is deterministic and not already loose. + def diff(a_name, b_name, loose_name=None): a = json.loads((HERE / f"run-{a_name}.json").read_text())