New BaseTest structure; new FlowStep sequence testing

This commit is contained in:
kdmukai
2023-03-04 14:21:15 -06:00
parent d9b4db26d7
commit c61de8522f
11 changed files with 512 additions and 173 deletions
+2 -2
View File
@@ -1,5 +1,5 @@
from dataclasses import dataclass
from typing import Any, List
from typing import Any, List, Tuple, Union
@@ -217,7 +217,7 @@ class SettingsEntry:
visibility: str = SettingsConstants.VISIBILITY__GENERAL
type: str = SettingsConstants.TYPE__ENABLED_DISABLED
help_text: str = None
selection_options: List[str] = None
selection_options: List[Tuple[Union[str,int],str]] = None
default_value: Any = None
def __post_init__(self):
+8 -13
View File
@@ -6,14 +6,14 @@ from binascii import hexlify
from embit import bip39
from embit.descriptor import Descriptor
from embit.networks import NETWORKS
from typing import List, Type
from typing import List
from seedsigner.controller import Controller
from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerCustomIconConstants
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 BaseScreen, LargeIconStatusScreen, LoadingScreenThread, QRDisplayScreen
from seedsigner.gui.screens.screen import LargeIconStatusScreen, LoadingScreenThread, QRDisplayScreen
from seedsigner.models.decode_qr import DecodeQR
from seedsigner.models.encode_qr import EncodeQR
from seedsigner.models.psbt_parser import PSBTParser
@@ -120,22 +120,16 @@ class SeedMnemonicEntryView(View):
def run(self):
ret = seed_screens.SeedMnemonicEntryScreen(
ret = self.run_screen(
seed_screens.SeedMnemonicEntryScreen,
title=f"Seed Word #{self.cur_word_index + 1}", # Human-readable 1-indexing!
initial_letters=list(self.cur_word) if self.cur_word else ["a"],
wordlist=Seed.get_wordlist(wordlist_language_code=self.settings.get_value(SettingsConstants.SETTING__WORDLIST_LANGUAGE)),
).display()
)
if ret == RET_CODE__BACK_BUTTON:
if self.cur_word_index > 0:
return Destination(BackStackView)
# return Destination(
# SeedMnemonicEntryView,
# view_args={
# "cur_word_index": self.cur_word_index - 1,
# "is_calc_final_word": self.is_calc_final_word
# }
# )
else:
self.controller.storage.discard_pending_mnemonic()
return Destination(MainMenuView)
@@ -185,13 +179,14 @@ class SeedMnemonicInvalidView(View):
DISCARD = ("Discard", None, None, "red")
button_data = [EDIT, DISCARD]
selected_menu_num = WarningScreen(
selected_menu_num = self.run_screen(
WarningScreen,
title="Invalid Mnemonic!",
status_headline=None,
text=f"Checksum failure; not a valid seed phrase.",
show_back_button=False,
button_data=button_data,
).display()
)
if button_data[selected_menu_num] == EDIT:
return Destination(SeedMnemonicEntryView, view_args={"cur_word_index": 0})
+24 -12
View File
@@ -1,7 +1,6 @@
from seedsigner.gui.components import FontAwesomeIconConstants, SeedSignerCustomIconConstants
from seedsigner.models.decode_qr import DecodeQR
from seedsigner.gui.components import SeedSignerCustomIconConstants
from .view import View, Destination, BackStackView, MainMenuView
from .view import View, Destination, MainMenuView
from seedsigner.gui.screens import (RET_CODE__BACK_BUTTON, ButtonListScreen, settings_screens)
from seedsigner.models.settings import SettingsConstants, SettingsDefinition
@@ -56,17 +55,17 @@ class SettingsMenuView(View):
title = "Dev Options"
next = None
screen = ButtonListScreen(
selected_menu_num = self.run_screen(
ButtonListScreen,
title=title,
is_button_text_centered=False,
button_data=button_data,
selected_button=selected_button,
scroll_y_initial_offset=self.initial_scroll,
)
selected_menu_num = screen.display()
# Preserve our scroll position in this Screen so we can return
initial_scroll = screen.buttons[0].scroll_y
initial_scroll = self.screen.buttons[0].scroll_y
if selected_menu_num == RET_CODE__BACK_BUTTON:
if self.visibility == SettingsConstants.VISIBILITY__GENERAL:
@@ -100,6 +99,9 @@ class SettingsEntryUpdateSelectionView(View):
self.settings_entry = SettingsDefinition.get_settings_entry(attr_name)
self.selected_button = None
self.parent_initial_scroll = parent_initial_scroll
# hook for test suite to override recursion in run()
self.is_test = False
def run(self):
@@ -123,14 +125,18 @@ class SettingsEntryUpdateSelectionView(View):
if not self.selected_button:
self.selected_button = 0
ret_value = settings_screens.SettingsEntryUpdateSelectionScreen(
screen_args = dict(
display_name=self.settings_entry.display_name,
help_text=self.settings_entry.help_text,
button_data=button_data,
selected_button=self.selected_button,
checked_buttons=checked_buttons,
settings_entry_type=self.settings_entry.type,
).display()
settings_entry_type=self.settings_entry.type,
)
ret_value = self.run_screen(
settings_screens.SettingsEntryUpdateSelectionScreen,
**screen_args
)
destination = None
settings_menu_view_destination = Destination(
@@ -178,7 +184,13 @@ class SettingsEntryUpdateSelectionView(View):
# All selects stay in place; re-initialize where in the list we left off
self.selected_button = ret_value
return self.run()
if self.is_test:
# Test suite can't handle the recursive call. Fake it by returning us here
# via a Destination back to this View.
return Destination(self.__class__, view_args=dict(attr_name=self.settings_entry.attr_name, parent_initial_scroll=self.parent_initial_scroll))
else:
return self.run()
@@ -187,7 +199,7 @@ class SettingsEntryUpdateSelectionView(View):
****************************************************************************"""
class IOTestView(View):
def run(self):
settings_screens.IOTestScreen().display()
self.run_screen(settings_screens.IOTestScreen)
return Destination(SettingsMenuView)
@@ -195,6 +207,6 @@ class IOTestView(View):
class DonateView(View):
def run(self):
settings_screens.DonateScreen().display()
self.run_screen(settings_screens.DonateScreen)
return Destination(SettingsMenuView)
+5 -3
View File
@@ -57,17 +57,19 @@ class View:
self.canvas_height = self.renderer.canvas_height
self.buttons = self.controller.buttons
self.screen = None
def run_screen(self, Screen_cls: Type[BaseScreen], **kwargs) -> Union[int,str]:
"""
Instantiates the View class' Screen_cls and runs its interactive display.
Instantiates the provided Screen_cls and runs its interactive display.
Returns the user's input upon completion.
"""
return Screen_cls(**kwargs).display()
self.screen = Screen_cls(**kwargs)
return self.screen.display()
def run(self, **kwargs):
def run(self, **kwargs) -> 'Destination':
raise Exception("Must implement in the child class")
+87
View File
@@ -0,0 +1,87 @@
import sys
from dataclasses import dataclass
from mock import MagicMock, patch
from typing import Callable, List, Type, Union
# Prevent importing modules w/Raspi hardware dependencies.
# These must precede any SeedSigner imports.
sys.modules['seedsigner.gui.renderer'] = MagicMock()
sys.modules['seedsigner.gui.screens.screensaver'] = MagicMock()
sys.modules['seedsigner.hardware.buttons'] = MagicMock()
sys.modules['seedsigner.hardware.camera'] = MagicMock()
sys.modules['seedsigner.hardware.microsd'] = MagicMock()
from seedsigner.controller import Controller
from seedsigner.models import Settings
from seedsigner.views.view import Destination, View
class BaseTest:
def reset_controller(self):
""" Wipe and re-initialize the Controller singleton for each test run """
Controller._instance = None
Controller.configure_instance()
def setup_class(self):
Settings.SETTINGS_FILENAME = "settings-test.json"
def teardown_class(self):
""" If settings were written to disk, delete """
import os
try:
print(f"attempting to remove {Settings.SETTINGS_FILENAME}")
os.remove(Settings.SETTINGS_FILENAME)
print(f"Removed {Settings.SETTINGS_FILENAME}")
except:
print(f"{Settings.SETTINGS_FILENAME} not found to be removed")
def setup_method(self):
self.reset_controller()
self.controller = Controller.get_instance()
self.settings = Settings.get_instance()
@dataclass
class FlowStep:
expected_view: Type[View] = None
run_before: Callable = None
screen_return_value: Union[int, str] = None
class FlowTest(BaseTest):
def run_sequence(self, initial_destination: Destination, sequence: List[FlowStep]) -> Destination:
"""
Runs the given sequence from the initial_destination, mocking out the associated
Screen and the scenario's return_value, receiving the resulting new Destination,
and repeating until the sequence is complete.
Returns the final Destination.
"""
next_destination = initial_destination
for flow_step in sequence:
try:
# Validate that the next Destination's View is what we expected
if flow_step.expected_view:
assert next_destination.View_cls == flow_step.expected_view
# Patch the `View.run_screen()` so we don't actually instantiate the Screen
qualname = ".".join([next_destination.View_cls.__module__, next_destination.View_cls.__name__])
with patch(qualname + ".run_screen") as mock_run_screen:
mock_run_screen.return_value = flow_step.screen_return_value
# Now we can run the View and grab its resulting next Destination
next_destination = next_destination.run(run_before=flow_step.run_before)
except Exception as e:
import traceback
traceback.print_exc()
print(f"{next_destination} | {flow_step}")
raise e
return next_destination
+3 -7
View File
@@ -1,25 +1,21 @@
import pytest
# Must import this before the Controller
from utils import reset_controller
from base import BaseTest
from seedsigner.controller import Controller
from seedsigner.models.settings_definition import SettingsConstants
class TestController:
def setup_method(self):
reset_controller()
class TestController(BaseTest):
def test_reset_controller(self):
""" The reset_controller util should completely reset the Controller singleton """
controller = Controller.get_instance()
controller.address_explorer_data = "foo"
reset_controller()
self.reset_controller()
controller = Controller.get_instance()
assert controller.address_explorer_data is None
-119
View File
@@ -1,119 +0,0 @@
from typing import List, Type, Union
from mock import patch
# Must import this before the Controller
from utils import reset_controller
from seedsigner.controller import Controller
from seedsigner.models.seed import Seed
from seedsigner.views.view import Destination, MainMenuView, View
from seedsigner.views import seed_views, scan_views
RUN_BEFORE = "run_before"
RETURN_VALUE = "return_value"
def run_flow_sequence(initial_destination: Destination, return_values: List[Union[int, str, dict]]) -> Destination:
"""
Runs the given sequence from the initial_destination, mocking out the associated
Screen and the scenario's return_value, receiving the resulting new Destination,
and repeating until the sequence is complete.
Returns the final Destination.
"""
next_destination = initial_destination
for return_value in return_values:
# Optionally modify the View_cls instance before run_screen is called
run_before = None
if type(return_value) == dict:
if RUN_BEFORE in return_value:
run_before = return_value[RUN_BEFORE]
# Patch the run_screen so we don't actually instantiate and execute the Screen
qualname = ".".join([next_destination.View_cls.__module__, next_destination.View_cls.__name__])
with patch(qualname + ".run_screen") as mock_run_screen:
mock_run_screen.return_value = return_value
# Now we can run the View and grab its resulting next Destination
next_destination = next_destination.run(run_before=run_before)
return next_destination
class TestFlows:
def setup_method(self):
reset_controller()
def test_home_scan_seedoptions_flow(self):
"""
Selecting "Scan" from the MainMenuView and scanning a SeedQR should enter the
Finalize Seed flow and end at the SeedOptionsView.
"""
def load_seed_into_decoder(view: scan_views.ScanView):
view.decoder.add_data("0000" * 11 + "0003")
sequence = [
0, # MainMenuView: ret SCAN
{RUN_BEFORE: load_seed_into_decoder, RETURN_VALUE: None}, # ScanView: read SeedQR
0, # SeedFinalizeView: ret DONE
]
destination = run_flow_sequence(Destination(MainMenuView), return_values=sequence)
# Should land on SeedOptionsView
assert destination.View_cls == seed_views.SeedOptionsView
def test_addressexplorer_seedfinalizescreen_flow(self):
"""
Finalizing a seed during the Address Explorer flow should return to the next
Address Explorer step upon completion.
"""
controller = Controller.get_instance()
seed = Seed(mnemonic=["abandon "* 11 + "about"])
controller.storage.set_pending_seed(seed)
# Default behavior should land at the SeedOptionsView
sequence = [
0, # SeedFinalizeView: ret DONE
]
destination = run_flow_sequence(Destination(seed_views.SeedFinalizeView), return_values=sequence)
assert destination.View_cls == seed_views.SeedOptionsView
# Reset
controller.storage.seeds.clear()
controller.storage.set_pending_seed(seed)
# Now set the flow and try again
controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER
# Finalize the new seed w/out passphrase
sequence = [
0, # SeedFinalizeView: ret DONE
None, # SeedOptionsView should auto-route away
]
destination = run_flow_sequence(Destination(seed_views.SeedFinalizeView), return_values=sequence)
# Flow should resume at Script Type selection
assert destination.View_cls == seed_views.SeedExportXpubScriptTypeView
# Reset
controller.storage.seeds.clear()
controller.storage.set_pending_seed(seed)
# Finalize the new seed w/passphrase
sequence = [
1, # SeedFinalizeView: ret PASSPHRASE
"mypassphrase", # SeedPassphraseView: ret passphrase str
1, # SeedReviewPassphraseView: ret DONE
None, # SeedOptionsView should auto-route away
]
destination = run_flow_sequence(Destination(seed_views.SeedFinalizeView), return_values=sequence)
# Flow should resume at Script Type selection
assert destination.View_cls == seed_views.SeedExportXpubScriptTypeView
+143
View File
@@ -0,0 +1,143 @@
# Must import this before the Controller
from base import FlowTest, FlowStep
from seedsigner.views.view import Destination, MainMenuView
from seedsigner.views import seed_views, scan_views
class TestSeedFlows(FlowTest):
def test_scan_seedqr_flow(self):
"""
Selecting "Scan" from the MainMenuView and scanning a SeedQR should enter the
Finalize Seed flow and end at the SeedOptionsView.
"""
def load_seed_into_decoder(view: scan_views.ScanView):
view.decoder.add_data("0000" * 11 + "0003")
destination = self.run_sequence(
Destination(MainMenuView),
sequence=[
FlowStep(
screen_return_value=0, # ret SCAN
),
FlowStep(
expected_view=scan_views.ScanView,
run_before=load_seed_into_decoder, # simulate read SeedQR
screen_return_value=None,
),
FlowStep(
expected_view=seed_views.SeedFinalizeView,
screen_return_value=0, # ret DONE
),
]
)
assert destination.View_cls == seed_views.SeedOptionsView
def test_mnemonic_entry_flow(self):
"""
Manually entering a mnemonic should land at the Finalize Seed flow and end at
the SeedOptionsView.
"""
def test_with_mnemonic(mnemonic):
sequence = [
FlowStep(
screen_return_value=1, # ret SEEDS
),
FlowStep(
expected_view=seed_views.SeedsMenuView,
# Nothing to return; auto-forwards to LoadSeedView
),
FlowStep(
expected_view=seed_views.LoadSeedView,
screen_return_value=1 if len(mnemonic) == 12 else 2, # ret TYPE_12WORD or TYPE_24WORD
),
]
# Now add each manual word entry step
for word in mnemonic:
sequence.append(
FlowStep(
expected_view=seed_views.SeedMnemonicEntryView,
screen_return_value=word
)
)
# With the mnemonic completely entered, we land on the SeedFinalizeView
sequence.append(
FlowStep(
expected_view=seed_views.SeedFinalizeView,
screen_return_value=0, # ret DONE
)
)
destination = self.run_sequence(
Destination(MainMenuView),
sequence=sequence
)
assert destination.View_cls == seed_views.SeedOptionsView
# Test data from iancoleman.io; 12- and 24-word mnemonic
test_with_mnemonic("tone flat shed cool census soul paddle boy flight fantasy stem social".split())
self.reset_controller()
test_with_mnemonic("cotton artefact spy mind wing there echo steak child oak awful host despair online bicycle divorce middle firm diamond rare execute chimney almost hollow".split())
def test_invalid_mnemonic(self):
""" Should be able to go back and edit or discard an invalid mnemonic """
# Test data from iancoleman.io
mnemonic = "blush twice taste dawn feed second opinion lazy thumb play neglect impact".split()
sequence = [
FlowStep(
screen_return_value=1 if len(mnemonic) == 12 else 2, # ret TYPE_12WORD or TYPE_24WORD
),
]
for word in mnemonic[:-1]:
sequence.append(
FlowStep(
expected_view=seed_views.SeedMnemonicEntryView,
screen_return_value=word
)
)
sequence += [
FlowStep(
expected_view=seed_views.SeedMnemonicEntryView,
screen_return_value="zoo" # But finish with an INVALID checksum word
),
FlowStep(
expected_view=seed_views.SeedMnemonicInvalidView,
screen_return_value=0 # ret EDIT
),
]
# Restarts from first word
for word in mnemonic[:-1]:
sequence.append(
FlowStep(
expected_view=seed_views.SeedMnemonicEntryView,
screen_return_value=word
)
)
sequence += [
FlowStep(
expected_view=seed_views.SeedMnemonicEntryView,
screen_return_value="zebra" # provide yet another invalid checksum word
),
FlowStep(
expected_view=seed_views.SeedMnemonicInvalidView,
screen_return_value=1 # ret DISCARD; this time we give up
),
]
destination = self.run_sequence(
Destination(seed_views.LoadSeedView),
sequence
)
assert destination.View_cls == MainMenuView
+157
View File
@@ -0,0 +1,157 @@
from mock import MagicMock
from typing import Type
# Must import this before any seedsigner imports
from base import FlowTest, FlowStep
from seedsigner.models import SettingsDefinition
from seedsigner.models.settings_definition import SettingsConstants
from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON
from seedsigner.views.view import Destination, MainMenuView, View
from seedsigner.views import settings_views
class TestSettingsFlows(FlowTest):
def test_persistent_settings(self):
""" Basic flow from MainMenuView to enable/disable persistent settings """
# Which option are we testing?
settings_entries = SettingsDefinition.get_settings_entries()
nth_entry = [s.attr_name for s in settings_entries].index(SettingsConstants.SETTING__PERSISTENT_SETTINGS)
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__PERSISTENT_SETTINGS)
def mock_settingsmenuview_screen(view: Type[View]):
# SettingsMenuView reaches into its `self.screen` so we need to mock it out
view.screen = MagicMock()
destination = self.run_sequence(
Destination(MainMenuView),
sequence=[
FlowStep(
screen_return_value=3, # ret SETTINGS
),
FlowStep(
expected_view=settings_views.SettingsMenuView,
run_before=mock_settingsmenuview_screen,
screen_return_value=nth_entry, # ret persistent settings
),
FlowStep(
expected_view=settings_views.SettingsEntryUpdateSelectionView,
screen_return_value=[s[0] for s in settings_entry.selection_options].index(SettingsConstants.OPTION__ENABLED),
),
]
)
assert destination.View_cls == settings_views.SettingsMenuView
def test_multiselect(self):
""" Multiselect Settings options should stay in-place; requires BACK to exit. """
# Which option are we testing?
settings_entry = SettingsDefinition.get_settings_entry(SettingsConstants.SETTING__COORDINATORS)
nth_entry = SettingsDefinition.get_settings_entries().index(settings_entry)
def mock_settingsmenuview_screen(view: Type[View]):
# SettingsMenuView reaches into its `self.screen` so we need to mock it out
view.screen = MagicMock()
def disable_recursion(view: Type[View]):
# The recursion in SettingsEntryUpdateSelectionView for multiselect settings
# won't work with our FlowTest sequence structure.
view.is_test = True
destination = self.run_sequence(
Destination(MainMenuView),
sequence=[
FlowStep(
screen_return_value=3, # ret SETTINGS
),
FlowStep(
expected_view=settings_views.SettingsMenuView,
run_before=mock_settingsmenuview_screen,
screen_return_value=nth_entry, # ret Coordinator software
),
FlowStep(
expected_view=settings_views.SettingsEntryUpdateSelectionView,
run_before=disable_recursion,
screen_return_value=0, # select/deselect first option
),
FlowStep(
expected_view=settings_views.SettingsEntryUpdateSelectionView,
run_before=disable_recursion,
screen_return_value=1, # select/deselect second option
),
FlowStep(
expected_view=settings_views.SettingsEntryUpdateSelectionView,
run_before=disable_recursion,
screen_return_value=1, # select/deselect second option
),
FlowStep(
expected_view=settings_views.SettingsEntryUpdateSelectionView,
screen_return_value=RET_CODE__BACK_BUTTON, # BACK to exit
),
]
)
assert destination.View_cls == settings_views.SettingsMenuView
def test_io_test(self):
""" Basic flow from MainMenuView to I/O Test View """
# I/O Test will be the nth_entry after we include the submenu to "Advanced"
nth_entry = len(SettingsDefinition.get_settings_entries()) + 1
def mock_settingsmenuview_screen(view: Type[View]):
# SettingsMenuView reaches into its `self.screen` so we need to mock it out
view.screen = MagicMock()
destination = self.run_sequence(
Destination(MainMenuView),
sequence=[
FlowStep(
screen_return_value=3, # ret SETTINGS
),
FlowStep(
expected_view=settings_views.SettingsMenuView,
run_before=mock_settingsmenuview_screen,
screen_return_value=nth_entry, # ret I/O Test
),
FlowStep(
expected_view=settings_views.IOTestView,
screen_return_value=None, # ret value is ignored
),
]
)
# Exiting IOTestView should return us to the main SettingsMenuView
assert destination.View_cls == settings_views.SettingsMenuView
def test_donate(self):
""" Basic flow from MainMenuView to Donate View """
# DonateView will be the nth_entry after we include the submenu to "Advanced"
# and the I/O Test
nth_entry = len(SettingsDefinition.get_settings_entries()) + 2
def mock_settingsmenuview_screen(view: Type[View]):
# SettingsMenuView reaches into its `self.screen` so we need to mock it out
view.screen = MagicMock()
destination = self.run_sequence(
Destination(MainMenuView),
sequence=[
FlowStep(
screen_return_value=3, # ret SETTINGS
),
FlowStep(
expected_view=settings_views.SettingsMenuView,
run_before=mock_settingsmenuview_screen,
screen_return_value=nth_entry, # ret Donate
),
FlowStep(
expected_view=settings_views.DonateView,
screen_return_value=None, # ret value is ignored
),
]
)
# Exiting IOTestView should return us to the main SettingsMenuView
assert destination.View_cls == settings_views.SettingsMenuView
+83
View File
@@ -0,0 +1,83 @@
# Must import this before the Controller
from base import FlowTest, FlowStep
from seedsigner.controller import Controller
from seedsigner.models.seed import Seed
from seedsigner.views.view import Destination, MainMenuView
from seedsigner.views import seed_views, scan_views
class TestToolsFlows(FlowTest):
def test_addressexplorer_seedfinalizescreen_flow(self):
"""
Finalizing a seed during the Address Explorer flow should return to the next
Address Explorer step upon completion.
"""
controller = Controller.get_instance()
seed = Seed(mnemonic=["abandon "* 11 + "about"])
controller.storage.set_pending_seed(seed)
# Default behavior should land at the SeedOptionsView
destination = self.run_sequence(
Destination(seed_views.SeedFinalizeView),
sequence=[
FlowStep(
screen_return_value=0, # ret DONE
),
]
)
assert destination.View_cls == seed_views.SeedOptionsView
# Reset
controller.storage.seeds.clear()
controller.storage.set_pending_seed(seed)
# Now set the flow and try again
controller.resume_main_flow = Controller.FLOW__ADDRESS_EXPLORER
# Finalize the new seed w/out passphrase
destination = self.run_sequence(
Destination(seed_views.SeedFinalizeView),
sequence=[
FlowStep(
screen_return_value=0, # ret DONE
),
FlowStep(
expected_view=seed_views.SeedOptionsView,
screen_return_value=None, # should auto-route away w/out a selection
),
]
)
# Flow should resume at Script Type selection
assert destination.View_cls == seed_views.SeedExportXpubScriptTypeView
# Reset
controller.storage.seeds.clear()
controller.storage.set_pending_seed(seed)
# Finalize the new seed w/passphrase
destination = self.run_sequence(
Destination(seed_views.SeedFinalizeView),
sequence=[
FlowStep(
screen_return_value=1, # ret PASSPHRASE
),
FlowStep(
expected_view=seed_views.SeedAddPassphraseView,
screen_return_value="mypassphrase",
),
FlowStep(
expected_view=seed_views.SeedReviewPassphraseView,
screen_return_value=1, # ret DONE
),
FlowStep(
expected_view=seed_views.SeedOptionsView,
screen_return_value=None, # SeedOptionsView should auto-route away
),
]
)
# Flow should resume at Script Type selection
assert destination.View_cls == seed_views.SeedExportXpubScriptTypeView
-17
View File
@@ -1,17 +0,0 @@
import sys
from mock import MagicMock
# Prevent importing modules w/Raspi hardware dependencies
sys.modules['seedsigner.gui.renderer'] = MagicMock()
sys.modules['seedsigner.gui.screens.screensaver'] = MagicMock()
sys.modules['seedsigner.hardware.buttons'] = MagicMock()
sys.modules['seedsigner.hardware.camera'] = MagicMock()
sys.modules['seedsigner.hardware.microsd'] = MagicMock()
from seedsigner.controller import Controller
def reset_controller():
""" Wipe and re-initialize the Controller singleton for each test run """
Controller._instance = None
Controller.configure_instance()