From 8aa382d146af1249dc48b926cf7a427241306eec Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Wed, 7 May 2025 12:27:30 +0000 Subject: [PATCH 1/4] Test for removing profile while unlocked --- clive/__private/core/profile.py | 4 ++ .../clive_local_tools/cli/checkers.py | 9 ++- .../test_configure_profile_delete.py | 55 +++++++++++++++++++ tests/functional/cli/conftest.py | 29 ++++++++++ tests/functional/cli/test_locking.py | 32 ----------- 5 files changed, 95 insertions(+), 34 deletions(-) create mode 100644 tests/functional/cli/configure/test_configure_profile_delete.py diff --git a/clive/__private/core/profile.py b/clive/__private/core/profile.py index eb90814c73..de204a7d31 100644 --- a/clive/__private/core/profile.py +++ b/clive/__private/core/profile.py @@ -217,6 +217,10 @@ class Profile: def is_only_one_profile_saved(cls) -> bool: return len(cls.list_profiles()) == 1 + @classmethod + def is_profile_stored(cls, profile_name: str) -> bool: + return PersistentStorageService.is_profile_stored(profile_name) + @classmethod def create( # noqa: PLR0913 cls, diff --git a/tests/clive-local-tools/clive_local_tools/cli/checkers.py b/tests/clive-local-tools/clive_local_tools/cli/checkers.py index 948e88757d..ebd52fb8ed 100644 --- a/tests/clive-local-tools/clive_local_tools/cli/checkers.py +++ b/tests/clive-local-tools/clive_local_tools/cli/checkers.py @@ -5,9 +5,11 @@ from typing import TYPE_CHECKING import pytest from click.testing import Result +from clive.__private.cli.exceptions import CLINoProfileUnlockedError from clive.__private.core.formatters.humanize import humanize_bool from .cli_tester import CLITester +from .exceptions import CLITestCommandError if TYPE_CHECKING: from collections.abc import Callable @@ -18,8 +20,6 @@ if TYPE_CHECKING: from clive.__private.cli.types import AuthorityType from clive.__private.models.schemas import PublicKey - from .exceptions import CLITestCommandError - def assert_balances( context: CLITester | Result, @@ -197,3 +197,8 @@ def assert_unlocked_profile(context: CLITester | Result, profile_name: str) -> N output = _get_output(context, CLITester.show_profile) expected_output = f"Profile name: {profile_name}" assert_output_contains(expected_output, output, "show profile") + + +def assert_locked_profile(cli_tester: CLITester) -> None: + with pytest.raises(CLITestCommandError, match=CLINoProfileUnlockedError.MESSAGE): + cli_tester.show_profile() diff --git a/tests/functional/cli/configure/test_configure_profile_delete.py b/tests/functional/cli/configure/test_configure_profile_delete.py new file mode 100644 index 0000000000..7d9f9af243 --- /dev/null +++ b/tests/functional/cli/configure/test_configure_profile_delete.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +from clive.__private.core.profile import Profile +from clive_local_tools.cli.checkers import assert_locked_profile, assert_unlocked_profile +from clive_local_tools.data.constants import WORKING_ACCOUNT_PASSWORD +from clive_local_tools.testnet_block_log import ALT_WORKING_ACCOUNT1_NAME, WORKING_ACCOUNT_NAME + +if TYPE_CHECKING: + from clive_local_tools.cli.cli_tester import CLITester + + +async def test_remove_profile_when_locked(cli_tester_locked: CLITester) -> None: + # ACT + cli_tester_locked.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) + + # ASSERT + assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" + + +async def test_remove_currently_unlocked_profile(cli_tester: CLITester) -> None: + # ARRANGE + # profile would be saved in world_cm fixture but after deletion it is locked and saving gives errors + cli_tester.world.profile.skip_saving() + + # ACT + cli_tester.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) + + # ASSERT + assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" + assert_locked_profile(cli_tester) + + +async def test_remove_other_profile_when_unlocked(cli_tester_locked_with_second_profile: CLITester) -> None: + # ARRANGE + cli_tester = cli_tester_locked_with_second_profile + cli_tester.unlock(profile_name=WORKING_ACCOUNT_NAME, password_stdin=WORKING_ACCOUNT_PASSWORD) + + # ACT + cli_tester.configure_profile_delete(profile_name=ALT_WORKING_ACCOUNT1_NAME) + + # ASSERT + assert not Profile.is_profile_stored(ALT_WORKING_ACCOUNT1_NAME), ( + f"Profile {ALT_WORKING_ACCOUNT1_NAME} should be deleted" + ) + assert_unlocked_profile(cli_tester, WORKING_ACCOUNT_NAME) + + +async def test_remove_profile_session_token_not_set(cli_tester_without_session_token: CLITester) -> None: + # ACT + cli_tester_without_session_token.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) + + # ASSERT + assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" diff --git a/tests/functional/cli/conftest.py b/tests/functional/cli/conftest.py index b337e9ef9a..4b1421704f 100644 --- a/tests/functional/cli/conftest.py +++ b/tests/functional/cli/conftest.py @@ -14,10 +14,14 @@ from clive.__private.logger import logger from clive.__private.settings import safe_settings from clive_local_tools.cli.cli_tester import CLITester from clive_local_tools.data.constants import ( + ALT_WORKING_ACCOUNT1_KEY_ALIAS, + ALT_WORKING_ACCOUNT1_PASSWORD, WORKING_ACCOUNT_KEY_ALIAS, WORKING_ACCOUNT_PASSWORD, ) from clive_local_tools.testnet_block_log import ( + ALT_WORKING_ACCOUNT1_DATA, + ALT_WORKING_ACCOUNT1_NAME, KNOWN_ACCOUNTS, WATCHED_ACCOUNTS_NAMES, WORKING_ACCOUNT_DATA, @@ -124,6 +128,31 @@ async def cli_tester_locked(cli_tester: CLITester) -> CLITester: return cli_tester +@pytest.fixture +async def cli_tester_locked_with_second_profile(cli_tester_locked: CLITester) -> CLITester: + async with World() as world_cm: + await world_cm.create_new_profile_with_wallets(ALT_WORKING_ACCOUNT1_NAME, ALT_WORKING_ACCOUNT1_PASSWORD) + world_cm.profile.keys.add_to_import( + PrivateKeyAliased( + value=ALT_WORKING_ACCOUNT1_DATA.account.private_key, alias=f"{ALT_WORKING_ACCOUNT1_KEY_ALIAS}" + ) + ) + await world_cm.commands.sync_data_with_beekeeper() + await world_cm.commands.save_profile() # required for saving imported keys aliases + await world_cm.commands.lock() + world_cm.profile.skip_saving() # cannot save profile when it is locked because encryption is not possible + return cli_tester_locked + + +@pytest.fixture +async def cli_tester_without_remote_address( + beekeeper_remote_address_env_context_factory: EnvContextFactory, + cli_tester: CLITester, +) -> AsyncGenerator[CLITester]: + with beekeeper_remote_address_env_context_factory(None): + yield cli_tester + + @pytest.fixture async def cli_tester_without_session_token( beekeeper_session_token_env_context_factory: EnvContextFactory, diff --git a/tests/functional/cli/test_locking.py b/tests/functional/cli/test_locking.py index 338e173589..123453e0d3 100644 --- a/tests/functional/cli/test_locking.py +++ b/tests/functional/cli/test_locking.py @@ -5,52 +5,20 @@ from typing import TYPE_CHECKING import pytest from clive.__private.cli.exceptions import CLIBeekeeperRemoteAddressIsNotSetError, CLIBeekeeperSessionTokenNotSetError -from clive.__private.core.keys.keys import PrivateKeyAliased -from clive.__private.core.world import World from clive_local_tools.checkers.wallet_checkers import assert_wallet_unlocked, assert_wallets_locked from clive_local_tools.cli.checkers import assert_unlocked_profile from clive_local_tools.cli.exceptions import CLITestCommandError from clive_local_tools.data.constants import ( - ALT_WORKING_ACCOUNT1_KEY_ALIAS, ALT_WORKING_ACCOUNT1_PASSWORD, WORKING_ACCOUNT_PASSWORD, ) from clive_local_tools.testnet_block_log import ( - ALT_WORKING_ACCOUNT1_DATA, ALT_WORKING_ACCOUNT1_NAME, WORKING_ACCOUNT_NAME, ) if TYPE_CHECKING: - from collections.abc import AsyncGenerator - from clive_local_tools.cli.cli_tester import CLITester - from clive_local_tools.types import EnvContextFactory - - -@pytest.fixture -async def cli_tester_locked_with_second_profile(cli_tester_locked: CLITester) -> CLITester: - async with World() as world_cm: - await world_cm.create_new_profile_with_wallets(ALT_WORKING_ACCOUNT1_NAME, ALT_WORKING_ACCOUNT1_PASSWORD) - world_cm.profile.keys.add_to_import( - PrivateKeyAliased( - value=ALT_WORKING_ACCOUNT1_DATA.account.private_key, alias=f"{ALT_WORKING_ACCOUNT1_KEY_ALIAS}" - ) - ) - await world_cm.commands.sync_data_with_beekeeper() - await world_cm.commands.save_profile() # required for saving imported keys aliases - await world_cm.commands.lock() - world_cm.profile.skip_saving() # cannot save profile when it is locked because encryption is not possible - return cli_tester_locked - - -@pytest.fixture -async def cli_tester_without_remote_address( - beekeeper_remote_address_env_context_factory: EnvContextFactory, - cli_tester: CLITester, -) -> AsyncGenerator[CLITester]: - with beekeeper_remote_address_env_context_factory(None): - yield cli_tester async def test_negative_lock_without_remote_address(cli_tester_without_remote_address: CLITester) -> None: -- GitLab From 70721150b44b5811fef8d5aed82c34ec9fdf2290 Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Wed, 7 May 2025 11:47:52 +0000 Subject: [PATCH 2/4] Lock profile if in cli we delete currently unlocked --- .../cli/commands/abc/world_based_command.py | 7 ++++- .../cli/commands/configure/profile.py | 17 +++++++++--- clive/__private/core/commands/commands.py | 11 ++++++++ .../__private/core/commands/delete_profile.py | 27 +++++++++++++++++++ 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 clive/__private/core/commands/delete_profile.py diff --git a/clive/__private/cli/commands/abc/world_based_command.py b/clive/__private/cli/commands/abc/world_based_command.py index d26e097013..179174afce 100644 --- a/clive/__private/cli/commands/abc/world_based_command.py +++ b/clive/__private/cli/commands/abc/world_based_command.py @@ -47,6 +47,10 @@ class WorldBasedCommand(ContextualCLICommand[World], ABC): def is_session_token_set(self) -> bool: return safe_settings.beekeeper.is_session_token_set + @property + def should_validate_if_remote_address_required(self) -> bool: + return True + @property def should_validate_if_session_token_required(self) -> bool: return True @@ -56,7 +60,8 @@ class WorldBasedCommand(ContextualCLICommand[World], ABC): return True async def validate(self) -> None: - self._validate_beekeeper_remote_address_set() + if self.should_validate_if_remote_address_required: + self._validate_beekeeper_remote_address_set() if self.should_validate_if_session_token_required: self._validate_beekeeper_session_token_set() await self._validate_remote_beekeeper_running() diff --git a/clive/__private/cli/commands/configure/profile.py b/clive/__private/cli/commands/configure/profile.py index e9456afe50..45b57ea576 100644 --- a/clive/__private/cli/commands/configure/profile.py +++ b/clive/__private/cli/commands/configure/profile.py @@ -7,7 +7,6 @@ from getpass import getpass from beekeepy.exceptions import CommunicationError -from clive.__private.cli.commands.abc.external_cli_command import ExternalCLICommand from clive.__private.cli.commands.abc.forceable_cli_command import ForceableCLICommand from clive.__private.cli.commands.abc.world_based_command import WorldBasedCommand from clive.__private.cli.exceptions import ( @@ -98,11 +97,23 @@ class CreateProfile(WorldBasedCommand): @dataclass(kw_only=True) -class DeleteProfile(ExternalCLICommand, ForceableCLICommand): +class DeleteProfile(WorldBasedCommand, ForceableCLICommand): profile_name: str + @property + def should_validate_if_remote_address_required(self) -> bool: + return False + + @property + def should_validate_if_session_token_required(self) -> bool: + return False + + @property + def should_require_unlocked_wallet(self) -> bool: + return False + async def _run(self) -> None: try: - Profile.delete_by_name(self.profile_name, force=self.force) + await self.world.commands.delete_profile(profile_name_to_delete=self.profile_name, force=self.force) except MultipleProfileVersionsError as error: raise CLIMultipleProfileVersionsError(self.profile_name) from error diff --git a/clive/__private/core/commands/commands.py b/clive/__private/core/commands/commands.py index 1249cd4bd5..5702b81b33 100644 --- a/clive/__private/core/commands/commands.py +++ b/clive/__private/core/commands/commands.py @@ -28,6 +28,7 @@ from clive.__private.core.commands.data_retrieval.witnesses_data import ( WitnessesDataRetrieval, ) from clive.__private.core.commands.decrypt import Decrypt +from clive.__private.core.commands.delete_profile import DeleteProfile from clive.__private.core.commands.does_account_exist_in_node import DoesAccountExistsInNode from clive.__private.core.commands.encrypt import Encrypt from clive.__private.core.commands.find_accounts import FindAccounts @@ -156,6 +157,16 @@ class Commands[WorldT: World]: ) ) + async def delete_profile(self, *, profile_name_to_delete: str, force: bool = False) -> CommandWrapper: + return await self.__surround_with_exception_handlers( + DeleteProfile( + profile_name_to_delete=profile_name_to_delete, + profile_name_currently_unlocked=self._world.profile.name if self._world.is_profile_available else None, + session=self._world.beekeeper_manager.session if self._world.is_profile_available else None, + force=force, + ) + ) + async def does_account_exists_in_node(self, *, account_name: str) -> CommandWithResultWrapper[bool]: return await self.__surround_with_exception_handlers( DoesAccountExistsInNode(node=self._world.node, account_name=account_name) diff --git a/clive/__private/core/commands/delete_profile.py b/clive/__private/core/commands/delete_profile.py new file mode 100644 index 0000000000..e3af985fee --- /dev/null +++ b/clive/__private/core/commands/delete_profile.py @@ -0,0 +1,27 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import TYPE_CHECKING + +from clive.__private.core.commands.abc.command import Command +from clive.__private.core.commands.lock import Lock +from clive.__private.core.profile import Profile + +if TYPE_CHECKING: + from beekeepy import AsyncSession + + +@dataclass(kw_only=True) +class DeleteProfile(Command): + """Delete profile and lock if it was unlocked.""" + + profile_name_to_delete: str + profile_name_currently_unlocked: str | None + session: AsyncSession | None + force: bool = False + + async def _execute(self) -> None: + Profile.delete_by_name(self.profile_name_to_delete, force=self.force) + if self.profile_name_to_delete == self.profile_name_currently_unlocked: + assert self.session is not None, "Session must be provided to delete currently unlocked profile" + await Lock(session=self.session).execute() -- GitLab From 8ff298ba450b0820b95e657ea3db5c5c4b02a92c Mon Sep 17 00:00:00 2001 From: Marcin Sobczyk Date: Fri, 6 Jun 2025 12:08:35 +0000 Subject: [PATCH 3/4] Test for deleting profile without remote address set --- ...e_profile_delete_without_remote_address.py | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py diff --git a/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py b/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py new file mode 100644 index 0000000000..b1d86ee88b --- /dev/null +++ b/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest +from beekeepy import AsyncBeekeeper +from beekeepy.exceptions.common import InvalidatedStateByClosingBeekeeperError + +from clive.__private.core.profile import Profile +from clive.__private.settings import safe_settings +from clive_local_tools.testnet_block_log import WORKING_ACCOUNT_NAME + +if TYPE_CHECKING: + from collections.abc import AsyncGenerator + + from clive_local_tools.cli.cli_tester import CLITester + + +@pytest.fixture +async def beekeeper_local() -> AsyncGenerator[AsyncBeekeeper]: + """We need to handle error on double teardown of beekeeper.""" + with pytest.raises( + InvalidatedStateByClosingBeekeeperError + ): # we can use fixture beekeeper_local from conftest after issue #19 in beekeepey is resolved + async with await AsyncBeekeeper.factory( + settings=safe_settings.beekeeper.settings_local_factory() + ) as beekeeper_cm: + yield beekeeper_cm + + +async def test_remove_profile_remote_address_not_set( + beekeeper_local: AsyncBeekeeper, cli_tester_without_remote_address: CLITester +) -> None: + # ARRANGE + # profile can't be saved without beekeeper because there would be error during encryption + cli_tester_without_remote_address.world.profile.skip_saving() + # we call teardown here because configure_profile_delete runs second beekeeper when no remote_address is set + beekeeper_local.teardown() + + # ACT + cli_tester_without_remote_address.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) + + # ASSERT + assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" -- GitLab From 78a5b502dae4f7017989057ad738afe7c09627ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20=C5=BBebrak?= Date: Mon, 16 Jun 2025 14:10:06 +0200 Subject: [PATCH 4/4] Create and use ProfileChecker.assert_profile_is_stored --- .../clive_local_tools/checkers/profile_checker.py | 13 +++++++++++-- .../cli/configure/test_configure_profile_delete.py | 12 +++++------- ...nfigure_profile_delete_without_remote_address.py | 4 ++-- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/tests/clive-local-tools/clive_local_tools/checkers/profile_checker.py b/tests/clive-local-tools/clive_local_tools/checkers/profile_checker.py index e68ee674ce..b968946aeb 100644 --- a/tests/clive-local-tools/clive_local_tools/checkers/profile_checker.py +++ b/tests/clive-local-tools/clive_local_tools/checkers/profile_checker.py @@ -8,13 +8,12 @@ from clive.__private.core.commands.get_unlocked_encryption_wallet import GetUnlo from clive.__private.core.commands.get_unlocked_user_wallet import GetUnlockedUserWallet from clive.__private.core.commands.load_profile import LoadProfile from clive.__private.core.commands.unlock import Unlock +from clive.__private.core.profile import Profile from clive.__private.core.wallet_container import WalletContainer if TYPE_CHECKING: from collections.abc import AsyncGenerator, Iterable - from clive.__private.core.profile import Profile - class IsNotSet: """A class to represent a value that is not set.""" @@ -65,6 +64,16 @@ class ProfileChecker: unlocked_encryption_wallet=self._wallets.encryption_wallet, ).execute_with_result() + @classmethod + def assert_profile_is_stored(cls, profile_name: str, *, should_be_stored: bool = True, context: str = "") -> None: + is_stored = Profile.is_profile_stored(profile_name) + message = ( + "Profile is not stored while should be." if should_be_stored else "Profile is stored while should not be." + ) + if context: + message += f" Context: {context}" + assert is_stored == should_be_stored, message + async def assert_working_account(self, working_account: str | IsNotSet | None = None) -> None: """ Check working account of profile. diff --git a/tests/functional/cli/configure/test_configure_profile_delete.py b/tests/functional/cli/configure/test_configure_profile_delete.py index 7d9f9af243..a059f551f8 100644 --- a/tests/functional/cli/configure/test_configure_profile_delete.py +++ b/tests/functional/cli/configure/test_configure_profile_delete.py @@ -2,7 +2,7 @@ from __future__ import annotations from typing import TYPE_CHECKING -from clive.__private.core.profile import Profile +from clive_local_tools.checkers.profile_checker import ProfileChecker from clive_local_tools.cli.checkers import assert_locked_profile, assert_unlocked_profile from clive_local_tools.data.constants import WORKING_ACCOUNT_PASSWORD from clive_local_tools.testnet_block_log import ALT_WORKING_ACCOUNT1_NAME, WORKING_ACCOUNT_NAME @@ -16,7 +16,7 @@ async def test_remove_profile_when_locked(cli_tester_locked: CLITester) -> None: cli_tester_locked.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) # ASSERT - assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" + ProfileChecker.assert_profile_is_stored(WORKING_ACCOUNT_NAME, should_be_stored=False) async def test_remove_currently_unlocked_profile(cli_tester: CLITester) -> None: @@ -28,7 +28,7 @@ async def test_remove_currently_unlocked_profile(cli_tester: CLITester) -> None: cli_tester.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) # ASSERT - assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" + ProfileChecker.assert_profile_is_stored(WORKING_ACCOUNT_NAME, should_be_stored=False) assert_locked_profile(cli_tester) @@ -41,9 +41,7 @@ async def test_remove_other_profile_when_unlocked(cli_tester_locked_with_second_ cli_tester.configure_profile_delete(profile_name=ALT_WORKING_ACCOUNT1_NAME) # ASSERT - assert not Profile.is_profile_stored(ALT_WORKING_ACCOUNT1_NAME), ( - f"Profile {ALT_WORKING_ACCOUNT1_NAME} should be deleted" - ) + ProfileChecker.assert_profile_is_stored(ALT_WORKING_ACCOUNT1_NAME, should_be_stored=False) assert_unlocked_profile(cli_tester, WORKING_ACCOUNT_NAME) @@ -52,4 +50,4 @@ async def test_remove_profile_session_token_not_set(cli_tester_without_session_t cli_tester_without_session_token.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) # ASSERT - assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" + ProfileChecker.assert_profile_is_stored(WORKING_ACCOUNT_NAME, should_be_stored=False) diff --git a/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py b/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py index b1d86ee88b..c52294c86f 100644 --- a/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py +++ b/tests/functional/cli/configure/test_configure_profile_delete_without_remote_address.py @@ -6,8 +6,8 @@ import pytest from beekeepy import AsyncBeekeeper from beekeepy.exceptions.common import InvalidatedStateByClosingBeekeeperError -from clive.__private.core.profile import Profile from clive.__private.settings import safe_settings +from clive_local_tools.checkers.profile_checker import ProfileChecker from clive_local_tools.testnet_block_log import WORKING_ACCOUNT_NAME if TYPE_CHECKING: @@ -41,4 +41,4 @@ async def test_remove_profile_remote_address_not_set( cli_tester_without_remote_address.configure_profile_delete(profile_name=WORKING_ACCOUNT_NAME) # ASSERT - assert not Profile.is_profile_stored(WORKING_ACCOUNT_NAME), f"Profile {WORKING_ACCOUNT_NAME} should be deleted" + ProfileChecker.assert_profile_is_stored(WORKING_ACCOUNT_NAME, should_be_stored=False) -- GitLab