From 3c4c91a0af713b5763c8d558e9911fd7c7da4245 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Sat, 17 Jun 2023 15:36:32 -0400 Subject: [PATCH 01/10] baby steps for jdlcdl (post hard reset) --- src/seedsigner/views/seed_views.py | 20 +++++++++----- tests/test_flows_seed.py | 43 ++++++++++++++++++++++++------ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 02e160b5..5545ad06 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -617,11 +617,12 @@ class SeedExportXpubCoordinatorView(View): args["coordinator"] = self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[0] return Destination(SeedExportXpubWarningView, view_args=args, skip_current_view=True) - selected_menu_num = ButtonListScreen( + selected_menu_num = self.run_screen( + ButtonListScreen, title="Export Xpub", is_button_text_centered=False, button_data=self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS), - ).display() + ) if selected_menu_num < len(self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)): args["coordinator"] = self.settings.get_value(SettingsConstants.SETTING__COORDINATORS)[selected_menu_num] @@ -659,10 +660,11 @@ class SeedExportXpubWarningView(View): # Skip the WarningView entirely return destination - selected_menu_num = WarningScreen( + selected_menu_num = self.run_screen( + WarningScreen, status_headline="Privacy Leak!", text="""Xpub can be used to view all future transactions.""", - ).display() + ) if selected_menu_num == 0: # User clicked "I Understand" @@ -727,12 +729,13 @@ class SeedExportXpubDetailsView(View): finally: self.loading_screen.stop() - selected_menu_num = seed_screens.SeedExportXpubDetailsScreen( + selected_menu_num = self.run_screen( + seed_screens.SeedExportXpubDetailsScreen, fingerprint=fingerprint, has_passphrase=self.seed.passphrase is not None, derivation_path=derivation_path, xpub=xpub_base58, - ).display() + ) if selected_menu_num == 0: return Destination( @@ -784,7 +787,10 @@ class SeedExportXpubQRDisplayView(View): def run(self): - QRDisplayScreen(qr_encoder=self.qr_encoder).display() + self.run_screen( + QRDisplayScreen, + qr_encoder=self.qr_encoder + ) return Destination(MainMenuView) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index a8ea4e03..df93c073 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -1,6 +1,7 @@ # Must import test base before the Controller from base import BaseTest, FlowTest, FlowStep +from seedsigner.models.settings import SettingsConstants from seedsigner.models.seed import Seed from seedsigner.views.view import MainMenuView from seedsigner.views import seed_views, scan_views @@ -91,40 +92,66 @@ class TestSeedFlows(FlowTest): def test_export_xpub_flow(self): """ - Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the SeedOptionsView. + Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView """ # Load a finalized Seed into the Controller mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) self.controller.storage.finalize_pending_seed() + SIG_TYPE = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SIG_TYPES)[0] # 0: single-sig + SCRIPT_TYPE = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SCRIPT_TYPES)[0] # 0: native segwit + COORDINATOR = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[0] # 0: bluwallet + COORDINATOR = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[3] # 0: specter + + # TEST PASSES BUT RAISES WARNING + # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ + # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): + # TypeError: '<' not supported between instances of 'int' and 'MagicMock' + # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), - FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=seed_views.SeedExportXpubSigTypeView.SINGLE_SIG), - FlowStep(seed_views.SeedExportXpubScriptTypeView), - # TODO: Test is incomplete... + FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=SIG_TYPE), + FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=SCRIPT_TYPE), + FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=COORDINATOR), + FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), + FlowStep(MainMenuView), ] ) def test_export_xpub_skip_sig_type_flow(self): """ - + Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or coordinators """ # Load a finalized Seed into the Controller mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) self.controller.storage.finalize_pending_seed() + # exclusively set only one choice for each of sig_types, script_types and coordinators + self.settings.update(dict(sig_types=['ms'], script_types=['nes'], coordinators=['spa']), False) + + # TEST PASSES BUT RAISES WARNING + # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ + # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): + # TypeError: '<' not supported between instances of 'int' and 'MagicMock' + # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), - FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=seed_views.SeedExportXpubSigTypeView.SINGLE_SIG), - FlowStep(seed_views.SeedExportXpubScriptTypeView), - # TODO: Test is incomplete... + FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), + FlowStep(seed_views.SeedExportXpubScriptTypeView, is_redirect=True), + FlowStep(seed_views.SeedExportXpubCoordinatorView, is_redirect=True), + FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), + FlowStep(MainMenuView), ] ) From 7edc14fce8aa22c4781bad287c283ebd0f0b3b44 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 20 Jun 2023 05:57:22 -0400 Subject: [PATCH 02/10] exhaustive flow test for exporting standard xpubs --- tests/test_flows_seed.py | 69 ++++++++++++++++++++++++++-------------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index df93c073..b5d4b370 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -1,7 +1,7 @@ # Must import test base before the Controller from base import BaseTest, FlowTest, FlowStep -from seedsigner.models.settings import SettingsConstants +from seedsigner.models.settings import SettingsConstants as SC from seedsigner.models.seed import Seed from seedsigner.views.view import MainMenuView from seedsigner.views import seed_views, scan_views @@ -90,41 +90,62 @@ class TestSeedFlows(FlowTest): self.run_sequence(sequence) - def test_export_xpub_flow(self): + def test_export_xpub_standard_flow(self): """ Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView """ - # Load a finalized Seed into the Controller - mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() - self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) - self.controller.storage.finalize_pending_seed() - - SIG_TYPE = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SIG_TYPES)[0] # 0: single-sig - SCRIPT_TYPE = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SCRIPT_TYPES)[0] # 0: native segwit - COORDINATOR = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[0] # 0: bluwallet - COORDINATOR = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[3] # 0: specter # TEST PASSES BUT RAISES WARNING # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): # TypeError: '<' not supported between instances of 'int' and 'MagicMock' # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) - self.run_sequence( - initial_destination_view_args=dict(seed_num=0), - sequence=[ - FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), - FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=SIG_TYPE), - FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=SCRIPT_TYPE), - FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=COORDINATOR), - FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), - FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), - FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), - FlowStep(MainMenuView), - ] + def test_standard_xpubs(sig_tuple, script_tuple, coord_tuple): + self.run_sequence( + initial_destination_view_args=dict(seed_num=0), + sequence=[ + FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), + FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_tuple[1]), + FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=script_tuple[1]), + FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=coord_tuple[1]), + FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), + FlowStep(MainMenuView), + ] ) + + # Load a finalized Seed into the Controller + mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() + self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) + self.controller.storage.finalize_pending_seed() + + # these are (constant_value, display_name) tuples + sig_types = SC.ALL_SIG_TYPES + script_types = SC.ALL_SCRIPT_TYPES + coordinators = SC.ALL_COORDINATORS + + # enable non-defaults so they're available in views + self.settings.set_value(SC.SETTING__SIG_TYPES, [x for x,y in sig_types]) + self.settings.set_value(SC.SETTING__SCRIPT_TYPES, [x for x,y in script_types]) + self.settings.set_value(SC.SETTING__COORDINATORS, [x for x,y in coordinators]) + + # exhaustively test flows thru standard sig_types, script_types, and coordinators + for sig_tuple in sig_types: + for script_tuple in script_types: + for coord_tuple in coordinators: + # skip custom derivation + if script_tuple[0] == 'cus': + continue + # skip multisig taproot + elif sig_tuple[0] == "ms" and script_tuple[0] == 'tr': + continue + else: + print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, coord_tuple)) + test_standard_xpubs(sig_tuple, script_tuple, coord_tuple) - def test_export_xpub_skip_sig_type_flow(self): + def test_export_xpub_skip_non_option_flow(self): """ Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or coordinators """ From ac2d92c1d4e7263e9457653849395c8105f18341 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 20 Jun 2023 06:37:49 -0400 Subject: [PATCH 03/10] settings constants instead of lazy hardcoding --- tests/test_flows_seed.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index b5d4b370..3896beb5 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -135,10 +135,10 @@ class TestSeedFlows(FlowTest): for script_tuple in script_types: for coord_tuple in coordinators: # skip custom derivation - if script_tuple[0] == 'cus': + if script_tuple[0] == SC.CUSTOM_DERIVATION: continue # skip multisig taproot - elif sig_tuple[0] == "ms" and script_tuple[0] == 'tr': + elif sig_tuple[0] == SC.MULTISIG and script_tuple[0] == SC.TAPROOT: continue else: print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, coord_tuple)) @@ -155,7 +155,11 @@ class TestSeedFlows(FlowTest): self.controller.storage.finalize_pending_seed() # exclusively set only one choice for each of sig_types, script_types and coordinators - self.settings.update(dict(sig_types=['ms'], script_types=['nes'], coordinators=['spa']), False) + self.settings.update({ + SC.SETTING__SIG_TYPES: SC.MULTISIG, + SC.SETTING__SCRIPT_TYPES: SC.NESTED_SEGWIT, + SC.SETTING__COORDINATORS: SC.COORDINATOR__SPECTER_DESKTOP, + }, disable_missing_entries=False) # TEST PASSES BUT RAISES WARNING # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ From 1509df72b8c02ef4121625123bb6995df7631f35 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 20 Jun 2023 07:59:56 -0400 Subject: [PATCH 04/10] flow test: export xpub for custom derivation --- src/seedsigner/views/seed_views.py | 5 ++-- tests/test_flows_seed.py | 38 ++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 5545ad06..1e14f914 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -570,9 +570,10 @@ class SeedExportXpubCustomDerivationView(View): def run(self): - ret = seed_screens.SeedExportXpubCustomDerivationScreen( + ret = self.run_screen( + seed_screens.SeedExportXpubCustomDerivationScreen, initial_value=self.custom_derivation_path, - ).display() + ) if ret == RET_CODE__BACK_BUTTON: return Destination(BackStackView) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 3896beb5..0ae78d23 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -145,6 +145,44 @@ class TestSeedFlows(FlowTest): test_standard_xpubs(sig_tuple, script_tuple, coord_tuple) + def test_export_xpub_custom_derivation_flow(self): + """ + Export XPUB flow for custom derivation finishes at MainMenuView + """ + # Load a finalized Seed into the Controller + mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() + self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) + self.controller.storage.finalize_pending_seed() + + # enable custom derivation script_type setting (plus at least one more for a choice) + self.settings.set_value(SC.SETTING__SCRIPT_TYPES, [SC.NATIVE_SEGWIT, SC.NESTED_SEGWIT, SC.CUSTOM_DERIVATION]) + + # get display names to access button choices in the views (ugh: hardcoding, is there a better way?) + sig_type = self.settings.get_multiselect_value_display_names(SC.SETTING__SIG_TYPES)[0] # single sig + script_type = self.settings.get_multiselect_value_display_names(SC.SETTING__SCRIPT_TYPES)[2] # custom derivation + coordinator = self.settings.get_multiselect_value_display_names(SC.SETTING__COORDINATORS)[3] # specter + + # TEST PASSES BUT RAISES WARNING + # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ + # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): + # TypeError: '<' not supported between instances of 'int' and 'MagicMock' + # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) + self.run_sequence( + initial_destination_view_args=dict(seed_num=0), + sequence=[ + FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), + FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=sig_type), + FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=script_type), + FlowStep(seed_views.SeedExportXpubCustomDerivationView, screen_return_value="m/0'/0'"), + FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=coordinator), + FlowStep(seed_views.SeedExportXpubWarningView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubDetailsView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubQRDisplayView, screen_return_value=0), + FlowStep(MainMenuView), + ] + ) + + def test_export_xpub_skip_non_option_flow(self): """ Export XPUB flows w/o user choices when no other options for sig_types, script_types, and/or coordinators From b842f1fe1aba3b5b124cd55002e20dfe2972a32c Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 20 Jun 2023 08:51:07 -0400 Subject: [PATCH 05/10] local import of LoadScreenThread to avoid flow-test warnings --- src/seedsigner/views/seed_views.py | 5 ++++- tests/test_flows_seed.py | 15 --------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index 1e14f914..cad143de 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -13,7 +13,7 @@ from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerCustom from seedsigner.helpers import embit_utils from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, WarningScreen, DireWarningScreen, seed_screens) -from seedsigner.gui.screens.screen import LargeIconStatusScreen, LoadingScreenThread, QRDisplayScreen +from seedsigner.gui.screens.screen import LargeIconStatusScreen, QRDisplayScreen from seedsigner.models.decode_qr import DecodeQR from seedsigner.models.encode_qr import EncodeQR from seedsigner.models.psbt_parser import PSBTParser @@ -707,6 +707,9 @@ class SeedExportXpubDetailsView(View): selected_menu_num = 0 else: + # imported locally instead of at module level to avoid flow-test warnings: "comparing int to MagicMock" + from seedsigner.gui.screens.screen import LoadingScreenThread + # The derivation calc takes a few moments. Run the loading screen while we wait. self.loading_screen = LoadingScreenThread(text="Generating xpub...") self.loading_screen.start() diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 0ae78d23..187900a4 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -95,11 +95,6 @@ class TestSeedFlows(FlowTest): Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView """ - # TEST PASSES BUT RAISES WARNING - # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ - # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): - # TypeError: '<' not supported between instances of 'int' and 'MagicMock' - # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) def test_standard_xpubs(sig_tuple, script_tuple, coord_tuple): self.run_sequence( initial_destination_view_args=dict(seed_num=0), @@ -162,11 +157,6 @@ class TestSeedFlows(FlowTest): script_type = self.settings.get_multiselect_value_display_names(SC.SETTING__SCRIPT_TYPES)[2] # custom derivation coordinator = self.settings.get_multiselect_value_display_names(SC.SETTING__COORDINATORS)[3] # specter - # TEST PASSES BUT RAISES WARNING - # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ - # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): - # TypeError: '<' not supported between instances of 'int' and 'MagicMock' - # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ @@ -199,11 +189,6 @@ class TestSeedFlows(FlowTest): SC.SETTING__COORDINATORS: SC.COORDINATOR__SPECTER_DESKTOP, }, disable_missing_entries=False) - # TEST PASSES BUT RAISES WARNING - # File "seedsigner-dev/src/seedsigner/gui/components.py", line 316, in __post_init__ - # if not self.auto_line_break or full_text_width < self.supersampled_width - (2 * self.edge_padding * self.supersampling_factor): - # TypeError: '<' not supported between instances of 'int' and 'MagicMock' - # warnings.warn(pytest.PytestUnhandledThreadExceptionWarning(msg)) self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ From 39962b98d9148bffbf752d34e06d5a0958832e9a Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 22 Jun 2023 04:31:50 -0400 Subject: [PATCH 06/10] more consistent with existing convention --- tests/test_flows_seed.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 187900a4..d2f22876 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -1,7 +1,7 @@ # Must import test base before the Controller from base import BaseTest, FlowTest, FlowStep -from seedsigner.models.settings import SettingsConstants as SC +from seedsigner.models.settings import SettingsConstants from seedsigner.models.seed import Seed from seedsigner.views.view import MainMenuView from seedsigner.views import seed_views, scan_views @@ -116,24 +116,24 @@ class TestSeedFlows(FlowTest): self.controller.storage.finalize_pending_seed() # these are (constant_value, display_name) tuples - sig_types = SC.ALL_SIG_TYPES - script_types = SC.ALL_SCRIPT_TYPES - coordinators = SC.ALL_COORDINATORS + sig_types = SettingsConstants.ALL_SIG_TYPES + script_types = SettingsConstants.ALL_SCRIPT_TYPES + coordinators = SettingsConstants.ALL_COORDINATORS # enable non-defaults so they're available in views - self.settings.set_value(SC.SETTING__SIG_TYPES, [x for x,y in sig_types]) - self.settings.set_value(SC.SETTING__SCRIPT_TYPES, [x for x,y in script_types]) - self.settings.set_value(SC.SETTING__COORDINATORS, [x for x,y in coordinators]) + self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types]) + self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types]) + self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators]) # exhaustively test flows thru standard sig_types, script_types, and coordinators for sig_tuple in sig_types: for script_tuple in script_types: for coord_tuple in coordinators: # skip custom derivation - if script_tuple[0] == SC.CUSTOM_DERIVATION: + if script_tuple[0] == SettingsConstants.CUSTOM_DERIVATION: continue # skip multisig taproot - elif sig_tuple[0] == SC.MULTISIG and script_tuple[0] == SC.TAPROOT: + elif sig_tuple[0] == SettingsConstants.MULTISIG and script_tuple[0] == SettingsConstants.TAPROOT: continue else: print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, coord_tuple)) @@ -150,12 +150,16 @@ class TestSeedFlows(FlowTest): self.controller.storage.finalize_pending_seed() # enable custom derivation script_type setting (plus at least one more for a choice) - self.settings.set_value(SC.SETTING__SCRIPT_TYPES, [SC.NATIVE_SEGWIT, SC.NESTED_SEGWIT, SC.CUSTOM_DERIVATION]) + self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [ + SettingsConstants.NATIVE_SEGWIT, + SettingsConstants.NESTED_SEGWIT, + SettingsConstants.CUSTOM_DERIVATION + ]) # get display names to access button choices in the views (ugh: hardcoding, is there a better way?) - sig_type = self.settings.get_multiselect_value_display_names(SC.SETTING__SIG_TYPES)[0] # single sig - script_type = self.settings.get_multiselect_value_display_names(SC.SETTING__SCRIPT_TYPES)[2] # custom derivation - coordinator = self.settings.get_multiselect_value_display_names(SC.SETTING__COORDINATORS)[3] # specter + sig_type = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SIG_TYPES)[0] # single sig + script_type = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__SCRIPT_TYPES)[2] # custom derivation + coordinator = self.settings.get_multiselect_value_display_names(SettingsConstants.SETTING__COORDINATORS)[3] # specter self.run_sequence( initial_destination_view_args=dict(seed_num=0), @@ -184,9 +188,9 @@ class TestSeedFlows(FlowTest): # exclusively set only one choice for each of sig_types, script_types and coordinators self.settings.update({ - SC.SETTING__SIG_TYPES: SC.MULTISIG, - SC.SETTING__SCRIPT_TYPES: SC.NESTED_SEGWIT, - SC.SETTING__COORDINATORS: SC.COORDINATOR__SPECTER_DESKTOP, + SettingsConstants.SETTING__SIG_TYPES: SettingsConstants.MULTISIG, + SettingsConstants.SETTING__SCRIPT_TYPES: SettingsConstants.NESTED_SEGWIT, + SettingsConstants.SETTING__COORDINATORS: SettingsConstants.COORDINATOR__SPECTER_DESKTOP, }, disable_missing_entries=False) self.run_sequence( From e25de29a64cd2ce4d3a2ec403600eed2b3bed8cf Mon Sep 17 00:00:00 2001 From: Jean Do Date: Thu, 22 Jun 2023 07:24:31 -0400 Subject: [PATCH 07/10] Export xpub flow test for disabled sig/script/coord settings --- tests/test_flows_seed.py | 64 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index d2f22876..7090e2fd 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -1,6 +1,7 @@ # Must import test base before the Controller -from base import BaseTest, FlowTest, FlowStep +from base import BaseTest, FlowTest, FlowStep, FlowBasedTestUnexpectedViewError +import pytest from seedsigner.models.settings import SettingsConstants from seedsigner.models.seed import Seed from seedsigner.views.view import MainMenuView @@ -140,6 +141,67 @@ class TestSeedFlows(FlowTest): test_standard_xpubs(sig_tuple, script_tuple, coord_tuple) + def test_export_xpub_disabled_not_available_flow(self): + """ + If sig_type/script_type/coordinator disabled, then these options are not available + """ + # Load a finalized Seed into the Controller + mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split() + self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) + self.controller.storage.finalize_pending_seed() + + # these are (constant_value, display_name) tuples + sig_types = SettingsConstants.ALL_SIG_TYPES + script_types = SettingsConstants.ALL_SCRIPT_TYPES + coordinators = SettingsConstants.ALL_COORDINATORS + + # these are the disabled types that we will be testing + disabled_sig = SettingsConstants.MULTISIG + disabled_script = SettingsConstants.TAPROOT + disabled_coord = SettingsConstants.COORDINATOR__NUNCHUK + + # enable all but our target disabled type + self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types if x!=disabled_sig]) + self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types if x!=disabled_script]) + self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators if x!=disabled_coord]) + + # test that multisig is not an option via exception raised when NOT redirected to next step w/o a choice + with pytest.raises(FlowBasedTestUnexpectedViewError) as e: + self.run_sequence( + initial_destination_view_args=dict(seed_num=0), + sequence=[ + FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), + FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=disabled_sig), + FlowStep(seed_views.SeedExportXpubScriptTypeView), + ] + ) + + # test that taproot is not an option via exception raised when choice is taproot + with pytest.raises(FlowBasedTestUnexpectedViewError) as e: + self.run_sequence( + initial_destination_view_args=dict(seed_num=0), + sequence=[ + FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), + FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), + FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=disabled_script), + FlowStep(seed_views.SeedExportXpubCoordinatorView), + ] + ) + + # test that nunchuk is not an option via exception raised when choice is nunchuk + with pytest.raises(FlowBasedTestUnexpectedViewError) as e: + self.run_sequence( + initial_destination_view_args=dict(seed_num=0), + sequence=[ + FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), + FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), + FlowStep(seed_views.SeedExportXpubScriptTypeView, screen_return_value=0), + FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=disabled_coord), + FlowStep(seed_views.SeedExportXpubWarningView), + ] + ) + + def test_export_xpub_custom_derivation_flow(self): """ Export XPUB flow for custom derivation finishes at MainMenuView From 35b3dc65cd3e5088d448e4fb1bc50162ac382f08 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Tue, 4 Jul 2023 16:55:48 -0400 Subject: [PATCH 08/10] code cleanup * funcs internal to test_funcs shouldn't start with "test" * comments in test modules preferred to repeating comments in code * type hinting in code preferred to explaining w/ comments --- src/seedsigner/views/seed_views.py | 4 +--- tests/base.py | 2 +- tests/test_flows_seed.py | 20 ++++++++++---------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/seedsigner/views/seed_views.py b/src/seedsigner/views/seed_views.py index cad143de..c2458ee7 100644 --- a/src/seedsigner/views/seed_views.py +++ b/src/seedsigner/views/seed_views.py @@ -707,10 +707,8 @@ class SeedExportXpubDetailsView(View): selected_menu_num = 0 else: - # imported locally instead of at module level to avoid flow-test warnings: "comparing int to MagicMock" - from seedsigner.gui.screens.screen import LoadingScreenThread - # The derivation calc takes a few moments. Run the loading screen while we wait. + from seedsigner.gui.screens.screen import LoadingScreenThread self.loading_screen = LoadingScreenThread(text="Generating xpub...") self.loading_screen.start() diff --git a/tests/base.py b/tests/base.py index fb8f1bca..78d3c61a 100644 --- a/tests/base.py +++ b/tests/base.py @@ -26,7 +26,7 @@ class BaseTest: # Ensure there are no on-disk artifacts after running tests. Settings.SETTINGS_FILENAME = "settings-test.json" - # Mock out the loading screen so it can't spawn. + # Mock out the loading screen so it can't spawn. View classes must import locally! patch('seedsigner.gui.screens.screen.LoadingScreenThread').start() diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 7090e2fd..18628f1e 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -96,7 +96,7 @@ class TestSeedFlows(FlowTest): Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView """ - def test_standard_xpubs(sig_tuple, script_tuple, coord_tuple): + def flowtest_standard_xpub(sig_tuple, script_tuple, coord_tuple): self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ @@ -116,10 +116,10 @@ class TestSeedFlows(FlowTest): self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) self.controller.storage.finalize_pending_seed() - # these are (constant_value, display_name) tuples - sig_types = SettingsConstants.ALL_SIG_TYPES - script_types = SettingsConstants.ALL_SCRIPT_TYPES - coordinators = SettingsConstants.ALL_COORDINATORS + # these are lists of (constant_value, display_name) tuples + sig_types: list[tuple[str, str]] = SettingsConstants.ALL_SIG_TYPES + script_types: list[tuple[str, str]] = SettingsConstants.ALL_SCRIPT_TYPES + coordinators: list[tuple[str, str]] = SettingsConstants.ALL_COORDINATORS # enable non-defaults so they're available in views self.settings.set_value(SettingsConstants.SETTING__SIG_TYPES, [x for x,y in sig_types]) @@ -138,7 +138,7 @@ class TestSeedFlows(FlowTest): continue else: print('\n\ntest_standard_xpubs(%s, %s, %s)' % (sig_tuple, script_tuple, coord_tuple)) - test_standard_xpubs(sig_tuple, script_tuple, coord_tuple) + flowtest_standard_xpub(sig_tuple, script_tuple, coord_tuple) def test_export_xpub_disabled_not_available_flow(self): @@ -150,10 +150,10 @@ class TestSeedFlows(FlowTest): self.controller.storage.set_pending_seed(Seed(mnemonic=mnemonic)) self.controller.storage.finalize_pending_seed() - # these are (constant_value, display_name) tuples - sig_types = SettingsConstants.ALL_SIG_TYPES - script_types = SettingsConstants.ALL_SCRIPT_TYPES - coordinators = SettingsConstants.ALL_COORDINATORS + # these are lists of (constant_value, display_name) tuples + sig_types: list[tuple[str, str]] = SettingsConstants.ALL_SIG_TYPES + script_types: list[tuple[str, str]] = SettingsConstants.ALL_SCRIPT_TYPES + coordinators: list[tuple[str, str]] = SettingsConstants.ALL_COORDINATORS # these are the disabled types that we will be testing disabled_sig = SettingsConstants.MULTISIG From 01c4f51f3345212d02b0e6f60c7311c5a0ec31c4 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Fri, 7 Jul 2023 11:21:50 -0400 Subject: [PATCH 09/10] more precise flow-test exceptions --- tests/test_flows_seed.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/tests/test_flows_seed.py b/tests/test_flows_seed.py index 18628f1e..973518d3 100644 --- a/tests/test_flows_seed.py +++ b/tests/test_flows_seed.py @@ -1,5 +1,6 @@ # Must import test base before the Controller -from base import BaseTest, FlowTest, FlowStep, FlowBasedTestUnexpectedViewError +from base import BaseTest, FlowTest, FlowStep +from base import FlowTestUnexpectedViewException, FlowTestRunScreenNotExecutedException, FlowTestInvalidButtonDataSelectionException import pytest from seedsigner.models.settings import SettingsConstants @@ -165,31 +166,29 @@ class TestSeedFlows(FlowTest): self.settings.set_value(SettingsConstants.SETTING__SCRIPT_TYPES, [x for x,y in script_types if x!=disabled_script]) self.settings.set_value(SettingsConstants.SETTING__COORDINATORS, [x for x,y in coordinators if x!=disabled_coord]) - # test that multisig is not an option via exception raised when NOT redirected to next step w/o a choice - with pytest.raises(FlowBasedTestUnexpectedViewError) as e: + # test that multisig is not an option via exception raised when redirected to next step instead of having a choice + with pytest.raises(FlowTestRunScreenNotExecutedException) as e: self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), FlowStep(seed_views.SeedExportXpubSigTypeView, button_data_selection=disabled_sig), - FlowStep(seed_views.SeedExportXpubScriptTypeView), ] ) # test that taproot is not an option via exception raised when choice is taproot - with pytest.raises(FlowBasedTestUnexpectedViewError) as e: + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ FlowStep(seed_views.SeedOptionsView, button_data_selection=seed_views.SeedOptionsView.EXPORT_XPUB), FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), FlowStep(seed_views.SeedExportXpubScriptTypeView, button_data_selection=disabled_script), - FlowStep(seed_views.SeedExportXpubCoordinatorView), ] ) # test that nunchuk is not an option via exception raised when choice is nunchuk - with pytest.raises(FlowBasedTestUnexpectedViewError) as e: + with pytest.raises(FlowTestInvalidButtonDataSelectionException) as e: self.run_sequence( initial_destination_view_args=dict(seed_num=0), sequence=[ @@ -197,7 +196,6 @@ class TestSeedFlows(FlowTest): FlowStep(seed_views.SeedExportXpubSigTypeView, is_redirect=True), FlowStep(seed_views.SeedExportXpubScriptTypeView, screen_return_value=0), FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=disabled_coord), - FlowStep(seed_views.SeedExportXpubWarningView), ] ) From b843b6b9fcfc787a937403242b2d98fda1abcb10 Mon Sep 17 00:00:00 2001 From: Jean Do Date: Sat, 8 Jul 2023 13:18:46 -0400 Subject: [PATCH 10/10] pr_339 post-review comments implemented --- src/seedsigner/views/psbt_views.py | 18 ++++++------------ tests/base.py | 2 +- tests/test_flows_psbt.py | 4 ++-- tests/test_flows_settings.py | 3 +-- tests/test_flows_view.py | 11 +++++------ 5 files changed, 15 insertions(+), 23 deletions(-) diff --git a/src/seedsigner/views/psbt_views.py b/src/seedsigner/views/psbt_views.py index 7c7e2bf4..fc5b4763 100644 --- a/src/seedsigner/views/psbt_views.py +++ b/src/seedsigner/views/psbt_views.py @@ -12,7 +12,7 @@ from seedsigner.models.psbt_parser import PSBTParser from seedsigner.models.qr_type import QRType from seedsigner.models.settings import SettingsConstants from seedsigner.gui.screens.psbt_screens import PSBTOverviewScreen, PSBTMathScreen, PSBTAddressDetailsScreen, PSBTChangeDetailsScreen, PSBTFinalizeScreen -from seedsigner.gui.screens.screen import (RET_CODE__BACK_BUTTON, ButtonListScreen, DireWarningScreen, QRDisplayScreen, WarningScreen) +from seedsigner.gui.screens.screen import (RET_CODE__BACK_BUTTON, ButtonListScreen, DireWarningScreen, QRDisplayScreen) from .view import BackStackView, MainMenuView, NotYetImplementedView, View, Destination @@ -131,12 +131,6 @@ class PSBTOverviewView(View): else: num_self_transfer_outputs += 1 - spend_amount = psbt_parser.spend_amount - change_amount = psbt_parser.change_amount - fee_amount = psbt_parser.fee_amount - num_inputs = psbt_parser.num_inputs - destination_addresses = psbt_parser.destination_addresses - # Everything is set. Stop the loading screen if self.loading_screen: self.loading_screen.stop() @@ -144,13 +138,13 @@ class PSBTOverviewView(View): # Run the overview screen selected_menu_num = self.run_screen( PSBTOverviewScreen, - spend_amount=spend_amount, - change_amount=change_amount, - fee_amount=fee_amount, - num_inputs=num_inputs, + spend_amount=psbt_parser.spend_amount, + change_amount=psbt_parser.change_amount, + fee_amount=psbt_parser.fee_amount, + num_inputs=psbt_parser.num_inputs, num_self_transfer_outputs=num_self_transfer_outputs, num_change_outputs=num_change_outputs, - destination_addresses=destination_addresses + destination_addresses=psbt_parser.destination_addresses ) if selected_menu_num == RET_CODE__BACK_BUTTON: diff --git a/tests/base.py b/tests/base.py index 78d3c61a..72bd55f3 100644 --- a/tests/base.py +++ b/tests/base.py @@ -50,7 +50,7 @@ class BaseTest: if os.path.exists(Settings.SETTINGS_FILENAME): os.remove(Settings.SETTINGS_FILENAME) except: - print(f"{Settings.SETTINGS_FILENAME} not found to be removed") + print(f"{Settings.SETTINGS_FILENAME} could not be removed") @classmethod diff --git a/tests/test_flows_psbt.py b/tests/test_flows_psbt.py index f8d9c7fd..f50491a1 100644 --- a/tests/test_flows_psbt.py +++ b/tests/test_flows_psbt.py @@ -1,6 +1,6 @@ from base import FlowTest, FlowStep -from seedsigner.controller import Controller, StopFlowBasedTest +from seedsigner.controller import Controller from seedsigner.views.view import MainMenuView from seedsigner.views import scan_views, seed_views, psbt_views @@ -53,4 +53,4 @@ class TestPSBTFlows(FlowTest): FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT), FlowStep(psbt_views.PSBTSignedQRDisplayView), FlowStep(MainMenuView) - ]) \ No newline at end of file + ]) diff --git a/tests/test_flows_settings.py b/tests/test_flows_settings.py index 4bf61212..8c603170 100644 --- a/tests/test_flows_settings.py +++ b/tests/test_flows_settings.py @@ -3,7 +3,6 @@ import os # Must import test base before the Controller from base import FlowTest, FlowStep -from seedsigner.controller import StopFlowBasedTest from seedsigner.models import SettingsDefinition from seedsigner.models.settings import Settings from seedsigner.models.settings_definition import SettingsConstants @@ -28,7 +27,7 @@ class TestSettingsFlows(FlowTest): FlowStep(settings_views.SettingsMenuView, button_data_selection=settings_entry.display_name), FlowStep(settings_views.SettingsEntryUpdateSelectionView, button_data_selection=settings_entry.get_selection_option_display_name_by_value(SettingsConstants.OPTION__ENABLED)), FlowStep(settings_views.SettingsEntryUpdateSelectionView, screen_return_value=RET_CODE__BACK_BUTTON), - FlowStep(settings_views.SettingsMenuView, screen_return_value=StopFlowBasedTest()), + FlowStep(settings_views.SettingsMenuView), ]) # Settings file should now exist diff --git a/tests/test_flows_view.py b/tests/test_flows_view.py index 43c7b9f0..3f4d537a 100644 --- a/tests/test_flows_view.py +++ b/tests/test_flows_view.py @@ -3,7 +3,6 @@ from mock import patch # Must import test base before the Controller from base import FlowTest, FlowStep -from seedsigner.controller import StopFlowBasedTest from seedsigner.gui.screens.screen import RET_CODE__POWER_BUTTON from seedsigner.models.settings import Settings from seedsigner.views.tools_views import ToolsCalcFinalWordNumWordsView, ToolsMenuView @@ -21,7 +20,7 @@ class TestViewFlows(FlowTest): self.run_sequence([ FlowStep(MainMenuView, screen_return_value=RET_CODE__POWER_BUTTON), FlowStep(PowerOptionsView, button_data_selection=PowerOptionsView.RESET), - FlowStep(RestartView, screen_return_value=StopFlowBasedTest()), + FlowStep(RestartView), ]) @@ -33,7 +32,7 @@ class TestViewFlows(FlowTest): self.run_sequence([ FlowStep(MainMenuView, screen_return_value=RET_CODE__POWER_BUTTON), FlowStep(PowerOptionsView, button_data_selection=PowerOptionsView.POWER_OFF), - FlowStep(PowerOffView, screen_return_value=StopFlowBasedTest()), + FlowStep(PowerOffView), ]) # And again, but this time as if we were in the SeedSigner OS @@ -42,7 +41,7 @@ class TestViewFlows(FlowTest): FlowStep(MainMenuView, screen_return_value=RET_CODE__POWER_BUTTON), FlowStep(PowerOptionsView, button_data_selection=PowerOptionsView.POWER_OFF), FlowStep(PowerOffView), # returns BackStackView - FlowStep(PowerOptionsView, screen_return_value=StopFlowBasedTest()), + FlowStep(PowerOptionsView), ]) @@ -58,7 +57,7 @@ class TestViewFlows(FlowTest): self.run_sequence([ FlowStep(IncompleteView), FlowStep(NotYetImplementedView), - FlowStep(MainMenuView, screen_return_value=StopFlowBasedTest()), + FlowStep(MainMenuView), ]) @@ -71,5 +70,5 @@ class TestViewFlows(FlowTest): FlowStep(ToolsMenuView, button_data_selection=ToolsMenuView.KEYBOARD), FlowStep(ToolsCalcFinalWordNumWordsView, screen_return_value=Exception("Test exception")), # <-- force an exception FlowStep(UnhandledExceptionView), - FlowStep(MainMenuView, screen_return_value=StopFlowBasedTest()), + FlowStep(MainMenuView), ])