From afcc042052b11e61a2c11ac0d2b89645037bbf9d Mon Sep 17 00:00:00 2001 From: kdmukai Date: Wed, 30 Aug 2023 23:24:09 -0500 Subject: [PATCH] SettingsQR tests; mocked `MicroSD.is_inserted` --- tests/base.py | 24 +++++++++++++-- tests/test_flows_settings.py | 58 ++++++++++++++++++++++++++++++++++-- tests/test_settings.py | 1 + 3 files changed, 78 insertions(+), 5 deletions(-) diff --git a/tests/base.py b/tests/base.py index fadf6ff7..e6ac5621 100644 --- a/tests/base.py +++ b/tests/base.py @@ -1,6 +1,6 @@ import sys from dataclasses import dataclass -from mock import MagicMock, patch +from mock import MagicMock, Mock, patch from typing import Callable # Prevent importing modules w/Raspi hardware dependencies. @@ -11,17 +11,27 @@ sys.modules['seedsigner.gui.toast'] = MagicMock() sys.modules['seedsigner.views.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, FlowBasedTestException, StopFlowBasedTest from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON, RET_CODE__POWER_BUTTON +from seedsigner.hardware.microsd import MicroSD from seedsigner.models.settings import Settings from seedsigner.views.view import Destination, MainMenuView, View + class BaseTest: + class MockMicroSD(Mock): + """ + A test suite-friendly replacement for `MicroSD` that gives a test explicit + control over the reported state of the SD card. + """ + # Tests are free to directly manipulate this attribute as needed + is_inserted: bool = True + + @classmethod def setup_class(cls): # Ensure there are no on-disk artifacts after running tests. @@ -30,6 +40,13 @@ class BaseTest: # Mock out the loading screen so it can't spawn. View classes must import locally! patch('seedsigner.gui.screens.screen.LoadingScreenThread').start() + # Instantiate the mocked MicroSD; hold on to the instance so tests can manipulate + # it later. + cls.mock_microsd = BaseTest.MockMicroSD() + + # And mock it over `MicroSD`'s instance + MicroSD.get_instance = Mock(return_value=cls.mock_microsd) + @classmethod def teardown_class(cls): @@ -62,11 +79,12 @@ class BaseTest: def setup_method(self): - """ Guarantee a clean/default Controller and Settings state for each test case """ + """ Guarantee a clean/default Controller, Settings, & MicroSD state for each test case """ BaseTest.reset_controller() BaseTest.reset_settings() self.controller = Controller.get_instance() self.settings = Settings.get_instance() + self.mock_microsd.is_inserted = True def teardown_method(self): diff --git a/tests/test_flows_settings.py b/tests/test_flows_settings.py index 1fb394a2..d9a62574 100644 --- a/tests/test_flows_settings.py +++ b/tests/test_flows_settings.py @@ -1,4 +1,5 @@ import os +from typing import Callable # Must import test base before the Controller from base import FlowTest, FlowStep @@ -6,13 +7,13 @@ from base import FlowTest, FlowStep from seedsigner.models.settings import Settings from seedsigner.models.settings_definition import SettingsDefinition, SettingsConstants from seedsigner.gui.screens.screen import RET_CODE__BACK_BUTTON +from seedsigner.hardware.microsd import MicroSD from seedsigner.views.view import MainMenuView -from seedsigner.views import settings_views +from seedsigner.views import scan_views, settings_views class TestSettingsFlows(FlowTest): - def test_persistent_settings(self): """ Basic flow from MainMenuView to enable/disable persistent settings """ # Which option are we testing? @@ -67,3 +68,56 @@ class TestSettingsFlows(FlowTest): FlowStep(settings_views.DonateView), FlowStep(settings_views.SettingsMenuView), ]) + + + def test_settingsqr(self): + """ + Scanning a SettingsQR should present the success screen and then return to + MainMenuView. + """ + def load_persistent_settingsqr_into_decoder(view: scan_views.ScanView): + settingsqr_data_persistent: str = "settings::v1 name=Total_noob_mode persistent=E coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E" + view.decoder.add_data(settingsqr_data_persistent) + + def load_not_persistent_settingsqr_into_decoder(view: scan_views.ScanView): + settingsqr_data_not_persistent: str = "settings::v1 name=Ephemeral_noob_mode persistent=D coords=spa,spd denom=thr network=M qr_density=M xpub_export=E sigs=ss scripts=nat xpub_details=E passphrase=E camera=0 compact_seedqr=E bip85=D priv_warn=E dire_warn=E partners=E" + view.decoder.add_data(settingsqr_data_not_persistent) + + def _run_test(initial_setting_state: str, load_settingsqr_into_decoder: Callable, expected_setting_state: str): + self.settings.set_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS, initial_setting_state) + self.run_sequence([ + FlowStep(MainMenuView, button_data_selection=MainMenuView.SCAN), + FlowStep(scan_views.ScanView, before_run=load_settingsqr_into_decoder), # simulate read message QR; ret val is ignored + FlowStep(settings_views.SettingsIngestSettingsQRView), # ret val is ignored + FlowStep(MainMenuView), + ]) + + assert self.settings.get_value(SettingsConstants.SETTING__PERSISTENT_SETTINGS) == expected_setting_state + + + # First load a SettingsQR that enables persistent settings + self.mock_microsd.is_inserted = True + assert MicroSD.get_instance().is_inserted is True + + _run_test( + initial_setting_state=SettingsConstants.OPTION__DISABLED, + load_settingsqr_into_decoder=load_persistent_settingsqr_into_decoder, + expected_setting_state=SettingsConstants.OPTION__ENABLED + ) + + # Then one that disables it + _run_test( + initial_setting_state=SettingsConstants.OPTION__ENABLED, + load_settingsqr_into_decoder=load_not_persistent_settingsqr_into_decoder, + expected_setting_state=SettingsConstants.OPTION__DISABLED + ) + + # Now try to enable persistent settings when the SD card is not inserted + self.mock_microsd.is_inserted = False + assert MicroSD.get_instance().is_inserted is False + + _run_test( + initial_setting_state=SettingsConstants.OPTION__DISABLED, + load_settingsqr_into_decoder=load_persistent_settingsqr_into_decoder, + expected_setting_state=SettingsConstants.OPTION__DISABLED + ) diff --git a/tests/test_settings.py b/tests/test_settings.py index 76026086..94ac626a 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -8,6 +8,7 @@ from seedsigner.models.settings_definition import SettingsConstants class TestSettings(BaseTest): @classmethod def setup_class(cls): + super().setup_class() cls.settings = Settings.get_instance()