diff --git a/tests/click_tests/recovery.py b/tests/click_tests/recovery.py index 51836683b3..d5ab66fcb9 100644 --- a/tests/click_tests/recovery.py +++ b/tests/click_tests/recovery.py @@ -68,6 +68,9 @@ def select_number_of_words( num_of_words: int = 20, unlock_repeated_backup=False, ) -> None: + layout = debug.read_layout() + assert TR.recovery__num_of_words in layout.text_content() + def select_bolt() -> "LayoutContent": # click the button from ValuePad if unlock_repeated_backup: @@ -114,7 +117,6 @@ def select_number_of_words( return debug.read_layout() if debug.layout_type is LayoutType.Bolt: - assert debug.read_layout().text_content() == TR.recovery__num_of_words layout = select_bolt() elif debug.layout_type is LayoutType.Caesar: debug.press_right() @@ -303,3 +305,36 @@ def finalize(debug: "DebugLink") -> None: layout = go_next(debug) assert layout is not None assert layout.main_component() == "Homescreen" + + +def cancel_recovery(debug: "DebugLink", recovery_type: str = "dry_run") -> None: + title = TR.translate(f"recovery__title_{recovery_type}") + cancel_title = TR.translate(f"recovery__title_cancel_{recovery_type}") + + layout = debug.read_layout() + assert title in layout.title() + + if debug.layout_type is LayoutType.Bolt: + debug.click(buttons.CANCEL) + layout = debug.read_layout() + assert cancel_title in layout.title() + debug.click(buttons.OK) + elif debug.layout_type is LayoutType.Caesar: + debug.press_left() + layout = debug.read_layout() + assert cancel_title in layout.title() + for _ in range(layout.page_count()): + debug.press_right() + elif debug.layout_type is LayoutType.Delizia: + # go to menu + debug.click(buttons.CORNER_BUTTON) + layout = debug.read_layout() + assert ( + TR.translate(f"recovery__cancel_{recovery_type}") in layout.text_content() + ) + debug.click(buttons.VERTICAL_MENU[0]) + else: + raise ValueError("Unknown model") + + layout = debug.read_layout() + assert layout.main_component() == "Homescreen" diff --git a/tests/click_tests/test_recovery.py b/tests/click_tests/test_recovery.py index 7161e774dd..7a348e321b 100644 --- a/tests/click_tests/test_recovery.py +++ b/tests/click_tests/test_recovery.py @@ -19,7 +19,7 @@ from typing import TYPE_CHECKING, Generator import pytest -from trezorlib import device, messages +from trezorlib import device, exceptions, messages from ..common import MNEMONIC12, MNEMONIC_SLIP39_BASIC_20_3of6 from . import recovery @@ -78,3 +78,46 @@ def test_recovery_bip39_previous_word(device_handler: "BackgroundDeviceHandler") bad_indexes = {1: seed_words[-1], 7: seed_words[0]} recovery.enter_seed_previous_correct(debug, seed_words, bad_indexes) recovery.finalize(debug) + + +def test_recovery_cancel_issue4613(device_handler: "BackgroundDeviceHandler"): + """Test for issue fixed in PR #4613: After aborting the recovery flow from host + side, it was impossible to exit recovery until device was restarted.""" + + debug = device_handler.debuglink() + + # initiate and confirm the recovery + device_handler.run(device.recover, type=messages.RecoveryType.DryRun) + recovery.confirm_recovery(debug, title="recovery__title_dry_run") + # select number of words + recovery.select_number_of_words(debug, num_of_words=12) + # abort the process running the recovery from host + device_handler.kill_task() + + # Now Trezor is hanging, waiting for user interaction, but nobody is communicating + # from the host side. + + # Reopen client and debuglink, closed by kill_task + device_handler.client.open() + debug = device_handler.debuglink() + + # Ping the Trezor with an Initialize message (listed in DO_NOT_RESTART) + try: + features = device_handler.client.call(messages.Initialize()) + except exceptions.Cancelled: + # due to a related problem, the first call in this situation will return + # a Cancelled failure. This test does not care, we just retry. + features = device_handler.client.call(messages.Initialize()) + + assert features.recovery_status == messages.RecoveryStatus.Recovery + # Trezor is sitting in recovery_homescreen now, waiting for the user to select + # number of words + recovery.select_number_of_words(debug, num_of_words=12) + # Trezor is waiting at "enter any word" screen, which has a Cancel button + recovery.cancel_recovery(debug) + + # We should be back at homescreen + layout = debug.read_layout() + assert layout.main_component() == "Homescreen" + features = device_handler.client.refresh_features() + assert features.recovery_status == messages.RecoveryStatus.Nothing