From 5d500d0b2494f83930a8a06f1455767482faf507 Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Thu, 4 Jun 2026 09:11:35 +0200 Subject: [PATCH] fix(sell): re-quote via the sell price endpoint on currency change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit onCurrencyChanged called getBuyPrice and rendered totalCost as the sell "You receive" amount — a buy-side quote on the Sell screen, inconsistent with onSharesChanged (which quotes via getSellPrice/estimatedAmount). Switch to the sell endpoint so the displayed amount matches what the user actually receives. The previous unit test pinned the getBuyPrice call as "likely on purpose"; that assumption was wrong (it contradicts the sell-conversion path) and is rewritten to assert the sell endpoint. Regression: test/screens/sell/cubits/sell_converter_cubit_test.dart Issue #657 — Part 4, finding S1 (HIGH). --- .../sell_converter/sell_converter_cubit.dart | 8 ++++-- .../cubits/sell_converter_cubit_test.dart | 28 +++++++++++++------ 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart b/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart index 93660b47..4cd89c90 100644 --- a/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart +++ b/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart @@ -87,11 +87,15 @@ class SellConverterCubit extends Cubit { // the seq guard. emit(state.copyWith(loading: true, currency: currency)); try { - final result = await _brokerbotService.getBuyPrice(state.sharesText, currency); + // Sell flow: a currency switch must re-quote via the SELL price endpoint + // (estimatedAmount = what the user receives), mirroring _convertShares. + // It previously called getBuyPrice/totalCost, showing a buy-side quote + // as the "You receive" amount (issue #657 P4 S1). + final result = await _brokerbotService.getSellPrice(state.sharesText, currency); if (isClosed || mySeq != _seq) return; emit( state.copyWith( - fiatText: result.totalCost.toStringAsFixed(_fractionDigits(state.sharesText)), + fiatText: result.estimatedAmount.toStringAsFixed(_fractionDigits(state.sharesText)), loading: false, ), ); diff --git a/test/screens/sell/cubits/sell_converter_cubit_test.dart b/test/screens/sell/cubits/sell_converter_cubit_test.dart index 15ac9c0f..68c64b3a 100644 --- a/test/screens/sell/cubits/sell_converter_cubit_test.dart +++ b/test/screens/sell/cubits/sell_converter_cubit_test.dart @@ -157,10 +157,12 @@ void main() { expect(cubit.state.fiatText, '125.50'); }); - test('onCurrencyChanged calls getBuyPrice (not getSellPrice) with current shares', () async { - // Note: the cubit uses BUY price for currency switch (likely on purpose - // to convert the displayed share count to a fiat estimate without the - // sell-side fee deduction). This test pins that contract. + test( + 'onCurrencyChanged re-quotes via the SELL price endpoint ' + '(issue #657 P4 S1 regression — used to call getBuyPrice)', () async { + // The previous behaviour called getBuyPrice/totalCost on a currency + // switch, rendering a buy-side quote as the sell "You receive" amount — + // inconsistent with onSharesChanged, which quotes via getSellPrice. when(() => service.getBuyPrice(any(), any())).thenAnswer( (_) async => BrokerbotBuyPriceDto( totalCost: 50.0, @@ -182,16 +184,26 @@ void main() { await cubit.onSharesChanged('10'); await Future.delayed(const Duration(milliseconds: 250)); + when(() => service.getSellPrice(any(), any())).thenAnswer( + (_) async => BrokerbotSellPriceDto( + shares: 10, + estimatedAmount: 95.5, + pricePerShare: 9.55, + currency: 'EUR', + ), + ); await cubit.onCurrencyChanged(Currency.eur); expect(cubit.state.currency, Currency.eur); - expect(cubit.state.fiatText, '50.00'); - verify(() => service.getBuyPrice('10', Currency.eur)).called(1); + // Sell-side estimate, NOT the buy-side totalCost (50.00). + expect(cubit.state.fiatText, '95.50'); + verify(() => service.getSellPrice('10', Currency.eur)).called(1); + verifyNever(() => service.getBuyPrice(any(), any())); }); - test('onCurrencyChanged flips currency even when getBuyPrice throws', () async { + test('onCurrencyChanged flips currency even when the sell quote throws', () async { when( - () => service.getBuyPrice(any(), any()), + () => service.getSellPrice(any(), any()), ).thenAnswer((_) async => throw Exception('throttle')); final cubit = SellConverterCubit(service);