diff --git a/compiler_admin/commands/convert.py b/compiler_admin/commands/convert.py index 019a6f7..049620f 100644 --- a/compiler_admin/commands/convert.py +++ b/compiler_admin/commands/convert.py @@ -1,3 +1,5 @@ +from argparse import Namespace + from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import ( GROUP_PARTNERS, @@ -18,7 +20,7 @@ ACCOUNT_TYPE_OU = {"contractor": OU_CONTRACTORS, "partner": OU_PARTNERS, "staff": OU_STAFF} -def convert(username: str, account_type: str) -> int: +def convert(args: Namespace) -> int: f"""Convert a user of one type to another. Args: username (str): The account to convert. Must exist already. @@ -27,7 +29,13 @@ def convert(username: str, account_type: str) -> int: Returns: A value indicating if the operation succeeded or failed. """ - account = user_account_name(username) + if not hasattr(args, "username"): + raise ValueError("username is required") + if not hasattr(args, "account_type"): + raise ValueError("account_type is required") + + account = user_account_name(args.username) + account_type = args.account_type if not user_exists(account): print(f"User does not exist: {account}") diff --git a/compiler_admin/commands/create.py b/compiler_admin/commands/create.py index 21f68b0..022a1ea 100644 --- a/compiler_admin/commands/create.py +++ b/compiler_admin/commands/create.py @@ -1,10 +1,11 @@ +from argparse import Namespace from typing import Sequence from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import GROUP_TEAM, add_user_to_group, CallGAMCommand, user_account_name, user_exists -def create(username: str, *args: Sequence[str]) -> int: +def create(args: Namespace, *extra: Sequence[str]) -> int: """Create a new user account in Compiler. The user's password is randomly generated and requires reset on first login. @@ -18,7 +19,10 @@ def create(username: str, *args: Sequence[str]) -> int: Returns: A value indicating if the operation succeeded or failed. """ - account = user_account_name(username) + if not hasattr(args, "username"): + raise ValueError("username is required") + + account = user_account_name(args.username) if user_exists(account): print(f"User already exists: {account}") @@ -26,7 +30,7 @@ def create(username: str, *args: Sequence[str]) -> int: print(f"User does not exist, continuing: {account}") - res = CallGAMCommand(("create", "user", account, "password", "random", "changepassword", *args)) + res = CallGAMCommand(("create", "user", account, "password", "random", "changepassword", *extra)) res += add_user_to_group(account, GROUP_TEAM) diff --git a/compiler_admin/commands/delete.py b/compiler_admin/commands/delete.py index 0883d55..6a32565 100644 --- a/compiler_admin/commands/delete.py +++ b/compiler_admin/commands/delete.py @@ -1,8 +1,10 @@ +from argparse import Namespace + from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import CallGAMCommand, user_account_name, user_exists -def delete(username: str) -> int: +def delete(args: Namespace) -> int: """Delete the user account. Args: @@ -10,12 +12,21 @@ def delete(username: str) -> int: Returns: A value indicating if the operation succeeded or failed. """ - account = user_account_name(username) + if not hasattr(args, "username"): + raise ValueError("username is required") + + account = user_account_name(args.username) if not user_exists(account): print(f"User does not exist: {account}") return RESULT_FAILURE + if getattr(args, "force", False) is False: + cont = input(f"Delete account {account}? (Y/n)") + if not cont.lower().startswith("y"): + print("Aborting delete.") + return RESULT_SUCCESS + print(f"User exists, deleting: {account}") res = CallGAMCommand(("delete", "user", account, "noactionifalias")) diff --git a/compiler_admin/commands/init.py b/compiler_admin/commands/init.py index d9f6f29..a004334 100644 --- a/compiler_admin/commands/init.py +++ b/compiler_admin/commands/init.py @@ -1,3 +1,4 @@ +from argparse import Namespace import os from pathlib import Path from shutil import rmtree @@ -21,7 +22,7 @@ def _clean_config_dir(config_dir: Path) -> None: rmtree(path) -def init(admin_user: str, gam: bool = False, gyb: bool = False) -> int: +def init(args: Namespace) -> int: """Initialize a new GAM project. See https://github.com/taers232c/GAMADV-XTD3/wiki/How-to-Install-Advanced-GAM @@ -36,9 +37,13 @@ def init(admin_user: str, gam: bool = False, gyb: bool = False) -> int: Returns: A value indicating if the operation succeeded or failed. """ + if not hasattr(args, "admin_user"): + raise ValueError("admin_user is required") + + admin_user = args.admin_user res = RESULT_SUCCESS - if gam: + if getattr(args, "gam", False): _clean_config_dir(GAM_CONFIG_PATH) # GAM is already installed via pyproject.toml res += CallGAMCommand(("config", "drive_dir", str(GAM_CONFIG_PATH), "verify")) @@ -46,7 +51,7 @@ def init(admin_user: str, gam: bool = False, gyb: bool = False) -> int: res += CallGAMCommand(("oauth", "create")) res += CallGAMCommand(("user", admin_user, "check", "serviceaccount")) - if gyb: + if getattr(args, "gyb", False): _clean_config_dir(GYB_CONFIG_PATH) # download GYB installer to config directory gyb = GYB_CONFIG_PATH / "gyb-install.sh" diff --git a/compiler_admin/commands/offboard.py b/compiler_admin/commands/offboard.py index 01dcec9..3fbee41 100644 --- a/compiler_admin/commands/offboard.py +++ b/compiler_admin/commands/offboard.py @@ -1,3 +1,4 @@ +from argparse import Namespace from tempfile import NamedTemporaryFile from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE @@ -12,7 +13,7 @@ ) -def offboard(username: str, alias: str = None) -> int: +def offboard(args: Namespace) -> int: """Fully offboard a user from Compiler. Args: @@ -22,17 +23,28 @@ def offboard(username: str, alias: str = None) -> int: Returns: A value indicating if the operation succeeded or failed. """ + if not hasattr(args, "username"): + raise ValueError("username is required") + + username = args.username account = user_account_name(username) if not user_exists(account): print(f"User does not exist: {account}") return RESULT_FAILURE + alias = getattr(args, "alias", None) alias_account = user_account_name(alias) if alias_account is not None and not user_exists(alias_account): print(f"Alias target user does not exist: {alias_account}") return RESULT_FAILURE + if getattr(args, "force", False) is False: + cont = input(f"Offboard account {account} {' (assigning alias to '+ alias_account +')' if alias else ''}? (Y/n)") + if not cont.lower().startswith("y"): + print("Aborting offboard.") + return RESULT_SUCCESS + print(f"User exists, offboarding: {account}") res = RESULT_SUCCESS diff --git a/compiler_admin/commands/restore.py b/compiler_admin/commands/restore.py index 9fc401a..4ee3527 100644 --- a/compiler_admin/commands/restore.py +++ b/compiler_admin/commands/restore.py @@ -1,10 +1,11 @@ +from argparse import Namespace import pathlib from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import USER_ARCHIVE, CallGYBCommand, user_account_name -def restore(username: str) -> int: +def restore(args: Namespace) -> int: """Restore an email backup from a prior offboarding. Args: @@ -12,7 +13,10 @@ def restore(username: str) -> int: Returns: A value indicating if the operation succeeded or failed. """ - account = user_account_name(username) + if not hasattr(args, "username"): + raise ValueError("username is required") + + account = user_account_name(args.username) backup_dir = f"GYB-GMail-Backup-{account}" if not pathlib.Path(backup_dir).exists(): diff --git a/compiler_admin/commands/signout.py b/compiler_admin/commands/signout.py index ddae0e9..657be90 100644 --- a/compiler_admin/commands/signout.py +++ b/compiler_admin/commands/signout.py @@ -1,8 +1,10 @@ +from argparse import Namespace + from compiler_admin.commands import RESULT_SUCCESS, RESULT_FAILURE from compiler_admin.services.google import CallGAMCommand, user_account_name, user_exists -def signout(username: str) -> int: +def signout(args: Namespace) -> int: """Signs the user out from all active sessions. Args: @@ -10,12 +12,21 @@ def signout(username: str) -> int: Returns: A value indicating if the operation succeeded or failed. """ - account = user_account_name(username) + if not hasattr(args, "username"): + raise ValueError("username is required") + + account = user_account_name(args.username) if not user_exists(account): print(f"User does not exist: {account}") return RESULT_FAILURE + if getattr(args, "force", False) is False: + cont = input(f"Signout account {account} from all active sessions? (Y/n)") + if not cont.lower().startswith("y"): + print("Aborting signout.") + return RESULT_SUCCESS + print(f"User exists, signing out from all active sessions: {account}") res = CallGAMCommand(("user", account, "signout")) diff --git a/compiler_admin/main.py b/compiler_admin/main.py index 1b24b31..760090f 100644 --- a/compiler_admin/main.py +++ b/compiler_admin/main.py @@ -49,14 +49,23 @@ def _subcmd(name, help, add_username_arg=True) -> argparse.ArgumentParser: "account_type", choices=ACCOUNT_TYPE_OU.keys(), help="Target account type for this conversion." ) - _subcmd("delete", help="Delete a user account.") + delete_parser = _subcmd("delete", help="Delete a user account.") + delete_parser.add_argument( + "--force", action="store_true", default=False, help="Don't ask for confirmation before deletion." + ) offboard_parser = _subcmd("offboard", help="Offboard a user account.") offboard_parser.add_argument("--alias", help="Account to assign username as an alias.") + offboard_parser.add_argument( + "--force", action="store_true", default=False, help="Don't ask for confirmation before offboarding." + ) _subcmd("restore", help="Restore an email backup from a prior offboarding.") - _subcmd("signout", help="Signs a user out from all active sessions.") + signout_parser = _subcmd("signout", help="Signs a user out from all active sessions.") + signout_parser.add_argument( + "--force", action="store_true", default=False, help="Don't ask for confirmation before signout." + ) if len(argv) == 0: argv = ["info"] @@ -66,19 +75,19 @@ def _subcmd(name, help, add_username_arg=True) -> argparse.ArgumentParser: if args.command == "info": return info() elif args.command == "create": - return create(args.username, *extra) + return create(args, *extra) elif args.command == "convert": - return convert(args.username, args.account_type) + return convert(args) elif args.command == "delete": - return delete(args.username) + return delete(args) elif args.command == "init": - return init(args.username, gam=args.gam, gyb=args.gyb) + return init(args) elif args.command == "offboard": - return offboard(args.username, args.alias) + return offboard(args) elif args.command == "restore": - return restore(args.username) + return restore(args) elif args.command == "signout": - return signout(args.username) + return signout(args) if __name__ == "__main__": diff --git a/tests/commands/test_convert.py b/tests/commands/test_convert.py index 37ba9e4..f102bba 100644 --- a/tests/commands/test_convert.py +++ b/tests/commands/test_convert.py @@ -1,3 +1,4 @@ +from argparse import Namespace import pytest from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS @@ -64,10 +65,25 @@ def mock_google_user_is_staff_false(mock_google_user_is_staff): return mock_google_user_is_staff +def test_convert_user_username_required(): + args = Namespace() + + with pytest.raises(ValueError, match="username is required"): + convert(args) + + +def test_convert_user_account_type_required(): + args = Namespace(username="username") + + with pytest.raises(ValueError, match="account_type is required"): + convert(args) + + def test_convert_user_does_not_exists(mock_google_user_exists, mock_google_move_user_ou): mock_google_user_exists.return_value = False - res = convert("username", "account_type") + args = Namespace(username="username", account_type="account_type") + res = convert(args) assert res == RESULT_FAILURE assert mock_google_move_user_ou.call_count == 0 @@ -75,7 +91,8 @@ def test_convert_user_does_not_exists(mock_google_user_exists, mock_google_move_ @pytest.mark.usefixtures("mock_google_user_exists_true") def test_convert_user_exists_bad_account_type(mock_google_move_user_ou): - res = convert("username", "account_type") + args = Namespace(username="username", account_type="account_type") + res = convert(args) assert res == RESULT_FAILURE assert mock_google_move_user_ou.call_count == 0 @@ -85,7 +102,8 @@ def test_convert_user_exists_bad_account_type(mock_google_move_user_ou): "mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_false" ) def test_convert_contractor(mock_google_move_user_ou): - res = convert("username", "contractor") + args = Namespace(username="username", account_type="contractor") + res = convert(args) assert res == RESULT_SUCCESS mock_google_move_user_ou.assert_called_once() @@ -93,7 +111,8 @@ def test_convert_contractor(mock_google_move_user_ou): @pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_true", "mock_google_user_is_staff_false") def test_convert_contractor_user_is_partner(mock_google_remove_user_from_group, mock_google_move_user_ou): - res = convert("username", "contractor") + args = Namespace(username="username", account_type="contractor") + res = convert(args) assert res == RESULT_SUCCESS assert mock_google_remove_user_from_group.call_count == 2 @@ -102,7 +121,8 @@ def test_convert_contractor_user_is_partner(mock_google_remove_user_from_group, @pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_true") def test_convert_contractor_user_is_staff(mock_google_remove_user_from_group, mock_google_move_user_ou): - res = convert("username", "contractor") + args = Namespace(username="username", account_type="contractor") + res = convert(args) assert res == RESULT_SUCCESS mock_google_remove_user_from_group.assert_called_once() @@ -113,7 +133,8 @@ def test_convert_contractor_user_is_staff(mock_google_remove_user_from_group, mo "mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_false" ) def test_convert_staff(mock_google_add_user_to_group, mock_google_move_user_ou): - res = convert("username", "staff") + args = Namespace(username="username", account_type="staff") + res = convert(args) assert res == RESULT_SUCCESS mock_google_add_user_to_group.assert_called_once() @@ -124,7 +145,8 @@ def test_convert_staff(mock_google_add_user_to_group, mock_google_move_user_ou): def test_convert_staff_user_is_partner( mock_google_add_user_to_group, mock_google_remove_user_from_group, mock_google_move_user_ou ): - res = convert("username", "staff") + args = Namespace(username="username", account_type="staff") + res = convert(args) assert res == RESULT_SUCCESS mock_google_remove_user_from_group.assert_called_once() @@ -134,7 +156,8 @@ def test_convert_staff_user_is_partner( @pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_true") def test_convert_staff_user_is_staff(mock_google_add_user_to_group, mock_google_move_user_ou): - res = convert("username", "staff") + args = Namespace(username="username", account_type="staff") + res = convert(args) assert res == RESULT_FAILURE assert mock_google_add_user_to_group.call_count == 0 @@ -145,7 +168,8 @@ def test_convert_staff_user_is_staff(mock_google_add_user_to_group, mock_google_ "mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_false" ) def test_convert_partner(mock_google_add_user_to_group, mock_google_move_user_ou): - res = convert("username", "partner") + args = Namespace(username="username", account_type="partner") + res = convert(args) assert res == RESULT_SUCCESS mock_google_add_user_to_group.call_count == 2 @@ -154,7 +178,8 @@ def test_convert_partner(mock_google_add_user_to_group, mock_google_move_user_ou @pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_true", "mock_google_user_is_staff_false") def test_convert_partner_user_is_partner(mock_google_add_user_to_group, mock_google_move_user_ou): - res = convert("username", "partner") + args = Namespace(username="username", account_type="partner") + res = convert(args) assert res == RESULT_FAILURE assert mock_google_add_user_to_group.call_count == 0 @@ -163,7 +188,8 @@ def test_convert_partner_user_is_partner(mock_google_add_user_to_group, mock_goo @pytest.mark.usefixtures("mock_google_user_exists_true", "mock_google_user_is_partner_false", "mock_google_user_is_staff_true") def test_convert_partner_user_is_staff(mock_google_add_user_to_group, mock_google_move_user_ou): - res = convert("username", "partner") + args = Namespace(username="username", account_type="partner") + res = convert(args) assert res == RESULT_SUCCESS mock_google_add_user_to_group.assert_called_once() diff --git a/tests/commands/test_create.py b/tests/commands/test_create.py index 7ea8b0c..90d5ce5 100644 --- a/tests/commands/test_create.py +++ b/tests/commands/test_create.py @@ -1,3 +1,4 @@ +from argparse import Namespace import pytest from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS @@ -19,10 +20,18 @@ def mock_google_add_user_to_group(mock_google_add_user_to_group): return mock_google_add_user_to_group(MODULE) +def test_create_user_username_required(): + args = Namespace() + + with pytest.raises(ValueError, match="username is required"): + create(args) + + def test_create_user_exists(mock_google_user_exists): mock_google_user_exists.return_value = True - res = create("username") + args = Namespace(username="username") + res = create(args) assert res == RESULT_FAILURE @@ -30,7 +39,8 @@ def test_create_user_exists(mock_google_user_exists): def test_create_user_does_not_exists(mock_google_user_exists, mock_google_CallGAMCommand, mock_google_add_user_to_group): mock_google_user_exists.return_value = False - res = create("username") + args = Namespace(username="username") + res = create(args) assert res == RESULT_SUCCESS diff --git a/tests/commands/test_delete.py b/tests/commands/test_delete.py index 2b9d9be..7728cc9 100644 --- a/tests/commands/test_delete.py +++ b/tests/commands/test_delete.py @@ -1,9 +1,24 @@ +from argparse import Namespace import pytest from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.delete import delete, __name__ as MODULE +@pytest.fixture +def mock_input_yes(mock_input): + fix = mock_input(MODULE) + fix.return_value = "y" + return fix + + +@pytest.fixture +def mock_input_no(mock_input): + fix = mock_input(MODULE) + fix.return_value = "n" + return fix + + @pytest.fixture def mock_google_user_exists(mock_google_user_exists): return mock_google_user_exists(MODULE) @@ -14,22 +29,44 @@ def mock_google_CallGAMCommand(mock_google_CallGAMCommand): return mock_google_CallGAMCommand(MODULE) -def test_delete_user_exists(mock_google_user_exists, mock_google_CallGAMCommand): +def test_delete_user_username_required(): + args = Namespace() + + with pytest.raises(ValueError, match="username is required"): + delete(args) + + +@pytest.mark.usefixtures("mock_input_yes") +def test_delete_confirm_yes(mock_google_user_exists, mock_google_CallGAMCommand): mock_google_user_exists.return_value = True - res = delete("username") + args = Namespace(username="username") + res = delete(args) assert res == RESULT_SUCCESS mock_google_CallGAMCommand.assert_called_once() - call_args = " ".join(mock_google_CallGAMCommand.call_args[0][0]) - assert "delete user" in call_args + call_args = mock_google_CallGAMCommand.call_args.args[0] + assert "delete" in call_args + assert "user" in call_args assert "noactionifalias" in call_args +@pytest.mark.usefixtures("mock_input_no") +def test_delete_confirm_no(mock_google_user_exists, mock_google_CallGAMCommand): + mock_google_user_exists.return_value = True + + args = Namespace(username="username") + res = delete(args) + + assert res == RESULT_SUCCESS + mock_google_CallGAMCommand.assert_not_called() + + def test_delete_user_does_not_exist(mock_google_user_exists, mock_google_CallGAMCommand): mock_google_user_exists.return_value = False - res = delete("username") + args = Namespace(username="username") + res = delete(args) assert res == RESULT_FAILURE assert mock_google_CallGAMCommand.call_count == 0 diff --git a/tests/commands/test_init.py b/tests/commands/test_init.py index 88f84dc..f508541 100644 --- a/tests/commands/test_init.py +++ b/tests/commands/test_init.py @@ -1,3 +1,4 @@ +from argparse import Namespace import pytest from compiler_admin.commands.init import _clean_config_dir, init, __name__ as MODULE @@ -51,8 +52,16 @@ def test_clean_config_dir(mocker, mock_GAM_CONFIG_PATH, mock_rmtree): assert mock_dir in mock_rmtree.call_args.args +def test_init_admin_user_required(): + args = Namespace() + + with pytest.raises(ValueError, match="admin_user is required"): + init(args) + + def test_init_default(mock_clean_config_dir, mock_google_CallGAMCommand, mock_subprocess_call): - init("username") + args = Namespace(admin_user="username") + init(args) assert mock_clean_config_dir.call_count == 0 assert mock_google_CallGAMCommand.call_count == 0 @@ -60,7 +69,8 @@ def test_init_default(mock_clean_config_dir, mock_google_CallGAMCommand, mock_su def test_init_gam(mock_GAM_CONFIG_PATH, mock_clean_config_dir, mock_google_CallGAMCommand): - init("username", gam=True, gyb=False) + args = Namespace(admin_user="username", gam=True, gyb=False) + init(args) mock_clean_config_dir.assert_called_once() assert mock_GAM_CONFIG_PATH in mock_clean_config_dir.call_args.args @@ -68,7 +78,8 @@ def test_init_gam(mock_GAM_CONFIG_PATH, mock_clean_config_dir, mock_google_CallG def test_init_gyb(mock_GYB_CONFIG_PATH, mock_clean_config_dir, mock_subprocess_call): - init("username", gam=False, gyb=True) + args = Namespace(admin_user="username", gam=False, gyb=True) + init(args) mock_clean_config_dir.assert_called_once() assert mock_GYB_CONFIG_PATH in mock_clean_config_dir.call_args.args diff --git a/tests/commands/test_offboard.py b/tests/commands/test_offboard.py index eaa1503..7ac3cd8 100644 --- a/tests/commands/test_offboard.py +++ b/tests/commands/test_offboard.py @@ -1,9 +1,24 @@ +from argparse import Namespace import pytest from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.offboard import offboard, __name__ as MODULE +@pytest.fixture +def mock_input_yes(mock_input): + fix = mock_input(MODULE) + fix.return_value = "y" + return fix + + +@pytest.fixture +def mock_input_no(mock_input): + fix = mock_input(MODULE) + fix.return_value = "n" + return fix + + @pytest.fixture def mock_NamedTemporaryFile(mock_NamedTemporaryFile): return mock_NamedTemporaryFile(MODULE, ["Overall Transfer Status: completed"]) @@ -34,7 +49,15 @@ def mock_google_CallGYBCommand(mock_google_CallGYBCommand): return mock_google_CallGYBCommand(MODULE) -def test_offboard_user_exists( +def test_offboard_user_username_required(): + args = Namespace() + + with pytest.raises(ValueError, match="username is required"): + offboard(args) + + +@pytest.mark.usefixtures("mock_input_yes") +def test_offboard_confirm_yes( mock_google_user_exists, mock_google_CallGAMCommand, mock_google_CallGYBCommand, @@ -44,7 +67,8 @@ def test_offboard_user_exists( ): mock_google_user_exists.return_value = True - res = offboard("username") + args = Namespace(username="username") + res = offboard(args) assert res == RESULT_SUCCESS assert mock_google_CallGAMCommand.call_count > 0 @@ -55,10 +79,32 @@ def test_offboard_user_exists( mock_commands_delete.assert_called_once() +@pytest.mark.usefixtures("mock_input_no") +def test_offboard_confirm_no( + mock_google_user_exists, + mock_google_CallGAMCommand, + mock_google_CallGYBCommand, + mock_commands_signout, + mock_commands_delete, +): + mock_google_user_exists.return_value = True + + args = Namespace(username="username") + res = offboard(args) + + assert res == RESULT_SUCCESS + mock_google_CallGAMCommand.assert_not_called() + mock_google_CallGYBCommand.assert_not_called() + + mock_commands_signout.assert_not_called() + mock_commands_delete.assert_not_called() + + def test_offboard_user_does_not_exist(mock_google_user_exists, mock_google_CallGAMCommand): mock_google_user_exists.return_value = False - res = offboard("username") + args = Namespace(username="username") + res = offboard(args) assert res == RESULT_FAILURE assert mock_google_CallGAMCommand.call_count == 0 @@ -69,7 +115,8 @@ def test_offboard_alias_user_does_not_exist(mock_google_user_exists, mock_google # https://stackoverflow.com/a/24897297 mock_google_user_exists.side_effect = [True, False] - res = offboard("username", "alias_username") + args = Namespace(username="username", alias="alias_username") + res = offboard(args) assert res == RESULT_FAILURE assert mock_google_user_exists.call_count == 2 diff --git a/tests/commands/test_restore.py b/tests/commands/test_restore.py index 2bf651f..f2e20d7 100644 --- a/tests/commands/test_restore.py +++ b/tests/commands/test_restore.py @@ -1,3 +1,4 @@ +from argparse import Namespace import pytest from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS @@ -14,10 +15,18 @@ def mock_google_CallGYBCommand(mock_google_CallGYBCommand): return mock_google_CallGYBCommand(MODULE) +def test_restore_user_username_required(): + args = Namespace() + + with pytest.raises(ValueError, match="username is required"): + restore(args) + + def test_restore_backup_exists(mock_Path_exists, mock_google_CallGYBCommand): mock_Path_exists.return_value = True - res = restore("username") + args = Namespace(username="username") + res = restore(args) assert res == RESULT_SUCCESS mock_google_CallGYBCommand.assert_called_once() @@ -26,7 +35,8 @@ def test_restore_backup_exists(mock_Path_exists, mock_google_CallGYBCommand): def test_restore_backup_does_not_exist(mocker, mock_Path_exists, mock_google_CallGYBCommand): mock_Path_exists.return_value = False - res = restore("username") + args = Namespace(username="username") + res = restore(args) assert res == RESULT_FAILURE assert mock_google_CallGYBCommand.call_count == 0 diff --git a/tests/commands/test_signout.py b/tests/commands/test_signout.py index 85ea10e..3caaf45 100644 --- a/tests/commands/test_signout.py +++ b/tests/commands/test_signout.py @@ -1,9 +1,24 @@ +from argparse import Namespace import pytest from compiler_admin.commands import RESULT_FAILURE, RESULT_SUCCESS from compiler_admin.commands.signout import signout, __name__ as MODULE +@pytest.fixture +def mock_input_yes(mock_input): + fix = mock_input(MODULE) + fix.return_value = "y" + return fix + + +@pytest.fixture +def mock_input_no(mock_input): + fix = mock_input(MODULE) + fix.return_value = "n" + return fix + + @pytest.fixture def mock_google_user_exists(mock_google_user_exists): return mock_google_user_exists(MODULE) @@ -14,21 +29,42 @@ def mock_google_CallGAMCommand(mock_google_CallGAMCommand): return mock_google_CallGAMCommand(MODULE) -def test_signout_user_exists(mock_google_user_exists, mock_google_CallGAMCommand): +def test_signout_user_username_required(): + args = Namespace() + + with pytest.raises(ValueError, match="username is required"): + signout(args) + + +@pytest.mark.usefixtures("mock_input_yes") +def test_signout_confirm_yes(mock_google_user_exists, mock_google_CallGAMCommand): mock_google_user_exists.return_value = True - res = signout("username") + args = Namespace(username="username") + res = signout(args) assert res == RESULT_SUCCESS mock_google_CallGAMCommand.assert_called_once() - call_args = " ".join(mock_google_CallGAMCommand.call_args[0][0]) + call_args = mock_google_CallGAMCommand.call_args.args[0] assert "user" in call_args and "signout" in call_args +@pytest.mark.usefixtures("mock_input_no") +def test_signout_confirm_no(mock_google_user_exists, mock_google_CallGAMCommand): + mock_google_user_exists.return_value = True + + args = Namespace(username="username") + res = signout(args) + + assert res == RESULT_SUCCESS + mock_google_CallGAMCommand.assert_not_called() + + def test_signout_user_does_not_exist(mock_google_user_exists, mock_google_CallGAMCommand): mock_google_user_exists.return_value = False - res = signout("username") + args = Namespace(username="username") + res = signout(args) assert res == RESULT_FAILURE - assert mock_google_CallGAMCommand.call_count == 0 + mock_google_CallGAMCommand.assert_not_called() diff --git a/tests/conftest.py b/tests/conftest.py index 6f2be86..2e27b45 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -22,6 +22,12 @@ def __mock_module_name(module): return _mock_module_name +@pytest.fixture +def mock_input(mock_module_name): + """Fixture returns a function that patches the built-in input in a given module.""" + return mock_module_name("input") + + @pytest.fixture def mock_commands_create(mock_module_name): """Fixture returns a function that patches commands.create in a given module.""" diff --git a/tests/test_main.py b/tests/test_main.py index 4b1b870..94c4b2e 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,3 +1,4 @@ +from argparse import Namespace import subprocess import pytest @@ -53,7 +54,7 @@ def test_main_create(mock_commands_create): mock_commands_create.assert_called_once() call_args = mock_commands_create.call_args.args - assert "username" in call_args + assert Namespace(command="create", username="username") in call_args def test_main_create_no_username(mock_commands_create): @@ -67,7 +68,7 @@ def test_main_convert(mock_commands_convert): mock_commands_convert.assert_called_once() call_args = mock_commands_convert.call_args.args - assert call_args == ("username", "contractor") + assert Namespace(command="convert", username="username", account_type="contractor") in call_args def test_main_convert_no_username(mock_commands_convert): @@ -87,7 +88,15 @@ def test_main_delete(mock_commands_delete): mock_commands_delete.assert_called_once() call_args = mock_commands_delete.call_args.args - assert "username" in call_args + assert Namespace(command="delete", username="username", force=False) in call_args + + +def test_main_delete_force(mock_commands_delete): + main(argv=["delete", "username", "--force"]) + + mock_commands_delete.assert_called_once() + call_args = mock_commands_delete.call_args.args + assert Namespace(command="delete", username="username", force=True) in call_args def test_main_delete_no_username(mock_commands_delete): @@ -112,24 +121,24 @@ def test_main_init_default(mock_commands_init): main(argv=["init", "username"]) mock_commands_init.assert_called_once() - assert mock_commands_init.call_args.args == ("username",) - assert mock_commands_init.call_args.kwargs == {"gam": False, "gyb": False} + call_args = mock_commands_init.call_args.args + assert Namespace(command="init", username="username", gam=False, gyb=False) in call_args def test_main_init_gam(mock_commands_init): main(argv=["init", "username", "--gam"]) mock_commands_init.assert_called_once() - assert mock_commands_init.call_args.args == ("username",) - assert mock_commands_init.call_args.kwargs == {"gam": True, "gyb": False} + call_args = mock_commands_init.call_args.args + assert Namespace(command="init", username="username", gam=True, gyb=False) in call_args def test_main_init_gyb(mock_commands_init): main(argv=["init", "username", "--gyb"]) mock_commands_init.assert_called_once() - assert mock_commands_init.call_args.args == ("username",) - assert mock_commands_init.call_args.kwargs == {"gam": False, "gyb": True} + call_args = mock_commands_init.call_args.args + assert Namespace(command="init", username="username", gam=False, gyb=True) in call_args def test_main_init_no_username(mock_commands_init): @@ -143,14 +152,23 @@ def test_main_offboard(mock_commands_offboard): mock_commands_offboard.assert_called_once() call_args = mock_commands_offboard.call_args.args - assert "username" in call_args + assert Namespace(command="offboard", username="username", alias=None, force=False) in call_args + + +def test_main_offboard_force(mock_commands_offboard): + main(argv=["offboard", "username", "--force"]) + + mock_commands_offboard.assert_called_once() + call_args = mock_commands_offboard.call_args.args + assert Namespace(command="offboard", username="username", alias=None, force=True) in call_args def test_main_offboard_with_alias(mock_commands_offboard): main(argv=["offboard", "username", "--alias", "anotheruser"]) mock_commands_offboard.assert_called_once() - assert mock_commands_offboard.call_args.args == ("username", "anotheruser") + call_args = mock_commands_offboard.call_args.args + assert Namespace(command="offboard", username="username", alias="anotheruser", force=False) in call_args def test_main_offboard_no_username(mock_commands_offboard): @@ -164,7 +182,7 @@ def test_main_restore(mock_commands_restore): mock_commands_restore.assert_called_once() call_args = mock_commands_restore.call_args.args - assert "username" in call_args + assert Namespace(command="restore", username="username") in call_args def test_main_restore_no_username(mock_commands_restore): @@ -178,7 +196,15 @@ def test_main_signout(mock_commands_signout): mock_commands_signout.assert_called_once() call_args = mock_commands_signout.call_args.args - assert "username" in call_args + assert Namespace(command="signout", username="username", force=False) in call_args + + +def test_main_signout_force(mock_commands_signout): + main(argv=["signout", "username", "--force"]) + + mock_commands_signout.assert_called_once() + call_args = mock_commands_signout.call_args.args + assert Namespace(command="signout", username="username", force=True) in call_args def test_main_signout_no_username(mock_commands_signout):