Skip to content

Commit

Permalink
Merge pull request #5 from jdlcdl/pr_339
Browse files Browse the repository at this point in the history
Additional test flows and cleanup
  • Loading branch information
kdmukai authored Jul 15, 2023
2 parents bf920be + b843b6b commit 4ce3fba
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 44 deletions.
18 changes: 6 additions & 12 deletions src/seedsigner/views/psbt_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
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

Expand Down Expand Up @@ -131,26 +131,20 @@ def run(self):
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()

# 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:
Expand Down
28 changes: 18 additions & 10 deletions src/seedsigner/views/seed_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
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
Expand Down Expand Up @@ -570,9 +570,10 @@ def __init__(self, seed_num: int, sig_type: str, script_type: str):


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)
Expand Down Expand Up @@ -617,11 +618,12 @@ def run(self):
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]
Expand Down Expand Up @@ -659,10 +661,11 @@ def run(self):
# 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"
Expand Down Expand Up @@ -705,6 +708,7 @@ def run(self):

else:
# 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()

Expand All @@ -727,12 +731,13 @@ def run(self):
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(
Expand Down Expand Up @@ -784,7 +789,10 @@ def __init__(self, seed_num: int, coordinator: str, derivation_path: str):


def run(self):
QRDisplayScreen(qr_encoder=self.qr_encoder).display()
self.run_screen(
QRDisplayScreen,
qr_encoder=self.qr_encoder
)

return Destination(MainMenuView)

Expand Down
4 changes: 2 additions & 2 deletions tests/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def setup_class(cls):
# 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()


Expand All @@ -50,7 +50,7 @@ def remove_settings(cls):
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
Expand Down
4 changes: 2 additions & 2 deletions tests/test_flows_psbt.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -53,4 +53,4 @@ def load_seed_into_decoder(view: scan_views.ScanView):
FlowStep(psbt_views.PSBTFinalizeView, button_data_selection=psbt_views.PSBTFinalizeView.APPROVE_PSBT),
FlowStep(psbt_views.PSBTSignedQRDisplayView),
FlowStep(MainMenuView)
])
])
159 changes: 149 additions & 10 deletions tests/test_flows_seed.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# Must import test base before the Controller
from base import BaseTest, FlowTest, FlowStep
from base import FlowTestUnexpectedViewException, FlowTestRunScreenNotExecutedException, FlowTestInvalidButtonDataSelectionException

import pytest
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
Expand Down Expand Up @@ -89,42 +92,178 @@ def test_invalid_mnemonic(self):
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 SeedOptionsView.
Selecting "Export XPUB" from the SeedOptionsView should enter the Export XPUB flow and end at the MainMenuView
"""

def flowtest_standard_xpub(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 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])
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] == SettingsConstants.CUSTOM_DERIVATION:
continue
# skip multisig 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))
flowtest_standard_xpub(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 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
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 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),
]
)

# test that taproot is not an option via exception raised when choice is taproot
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),
]
)

# test that nunchuk is not an option via exception raised when choice is nunchuk
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, screen_return_value=0),
FlowStep(seed_views.SeedExportXpubCoordinatorView, button_data_selection=disabled_coord),
]
)


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(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(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),
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.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_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
"""
# 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({
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(
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),
]
)

Expand Down
3 changes: 1 addition & 2 deletions tests/test_flows_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
# 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
Expand All @@ -28,7 +27,7 @@ def test_persistent_settings(self):
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
Expand Down
Loading

0 comments on commit 4ce3fba

Please sign in to comment.