diff --git a/README.md b/README.md index eac18b3..0c6881a 100644 --- a/README.md +++ b/README.md @@ -10,23 +10,17 @@ Built on top of [GAMADV-XTD3](https://github.com/taers232c/GAMADV-XTD3) and [GYB ```bash $ compiler-admin -h -usage: compiler-admin [-h] [-v] {info,init,create,convert,delete,offboard,reset-password,restore,signout} ... +usage: compiler-admin [-h] [-v] {info,init,user} ... positional arguments: - {info,init,create,convert,delete,offboard,reset-password,restore,signout} - info Print configuration and debugging information. - init Initialize a new admin project. This command should be run once before any others. - create Create a new user in the Compiler domain. - convert Convert a user account to a new type. - delete Delete a user account. - offboard Offboard a user account. - reset-password Reset a user's password to a randomly generated string. - restore Restore an email backup from a prior offboarding. - signout Signs a user out from all active sessions. + {info,init,user} The command to run + info Print configuration and debugging information. + init Initialize a new admin project. This command should be run once before any others. + user Work with users in the Compiler org. options: - -h, --help show this help message and exit - -v, --version show program's version number and exit + -h, --help show this help message and exit + -v, --version show program's version number and exit ``` ## Getting started @@ -60,11 +54,34 @@ The `init` commands follows the steps in the [GAMADV-XTD3 Wiki](https://github.c Additionally, GYB is used for Gmail backup/restore. See the [GYB Wiki](https://github.com/GAM-team/got-your-back/wiki) for more information. -## Creating a user +## Working with users + +The following commands are available to work with users in the Compiler domain: + +```bash +$ compiler-admin user -h +usage: compiler-admin user [-h] {create,convert,delete,offboard,reset-password,restore,signout} ... + +positional arguments: + {create,convert,delete,offboard,reset-password,restore,signout} + The user command to run. + create Create a new user in the Compiler domain. + convert Convert a user account to a new type. + delete Delete a user account. + offboard Offboard a user account. + reset-password Reset a user's password to a randomly generated string. + restore Restore an email backup from a prior offboarding. + signout Signs a user out from all active sessions. + +options: + -h, --help show this help message and exit +``` + +### Creating a user ```bash -$ compiler-admin create -h -usage: compiler-admin create [-h] [--notify NOTIFY] username +$ compiler-admin user create -h +usage: compiler-admin user create [-h] [--notify NOTIFY] username positional arguments: username A Compiler user account name, sans domain. @@ -76,11 +93,11 @@ options: Additional options are passed through to GAM, see more about [GAM user create](https://github.com/taers232c/GAMADV-XTD3/wiki/Users#create-a-user) -## Convert a user +### Convert a user ```bash -$ compiler-admin convert -h -usage: compiler-admin convert [-h] username {contractor,partner,staff} +$ compiler-admin user convert -h +usage: compiler-admin user convert [-h] username {contractor,partner,staff} positional arguments: username A Compiler user account name, sans domain. @@ -91,11 +108,11 @@ options: -h, --help show this help message and exit ``` -## Offboarding a user +### Offboarding a user ```bash -$ compiler-admin offboard -h -usage: compiler-admin offboard [-h] [--alias ALIAS] [--force] username +$ compiler-admin user offboard -h +usage: compiler-admin user offboard [-h] [--alias ALIAS] [--force] username positional arguments: username A Compiler user account name, sans domain. @@ -108,13 +125,13 @@ options: This script creates a local backup of `USER`'s inbox, see [Restore](#restore-an-email-backup) -## Restore an email backup +### Restore an email backup Retore a backup from a prior [Offboarding](#offboarding-a-user) into the `archive@compiler.la` account. ```bash -$ compiler-admin restore -h -usage: compiler-admin restore [-h] username +$ compiler-admin user restore -h +usage: compiler-admin user restore [-h] username positional arguments: username The user's account name, sans domain. diff --git a/compiler_admin/commands/info.py b/compiler_admin/commands/info.py index 7548952..9a75e3e 100644 --- a/compiler_admin/commands/info.py +++ b/compiler_admin/commands/info.py @@ -2,7 +2,7 @@ from compiler_admin.services.google import CallGAMCommand, CallGYBCommand -def info() -> int: +def info(*args, **kwargs) -> int: """Print information about this package and the GAM environment. Returns: diff --git a/compiler_admin/commands/init.py b/compiler_admin/commands/init.py index fde1f80..e628938 100644 --- a/compiler_admin/commands/init.py +++ b/compiler_admin/commands/init.py @@ -22,7 +22,7 @@ def _clean_config_dir(config_dir: Path) -> None: rmtree(path) -def init(args: Namespace) -> int: +def init(args: Namespace, *extras) -> int: """Initialize a new GAM project. See https://github.com/taers232c/GAMADV-XTD3/wiki/How-to-Install-Advanced-GAM diff --git a/compiler_admin/commands/user/__init__.py b/compiler_admin/commands/user/__init__.py new file mode 100644 index 0000000..5445211 --- /dev/null +++ b/compiler_admin/commands/user/__init__.py @@ -0,0 +1,18 @@ +from argparse import Namespace + +from compiler_admin.commands.user.create import create # noqa: F401 +from compiler_admin.commands.user.convert import convert # noqa: F401 +from compiler_admin.commands.user.delete import delete # noqa: F401 +from compiler_admin.commands.user.offboard import offboard # noqa: F401 +from compiler_admin.commands.user.reset_password import reset_password # noqa: F401 +from compiler_admin.commands.user.restore import restore # noqa: F401 +from compiler_admin.commands.user.signout import signout # noqa: F401 + + +def user(args: Namespace, *extra): + # try to call the subcommand function directly from local symbols + # if the subcommand function was imported above, it should exist in locals() + if args.subcommand in locals(): + locals()[args.subcommand](args, *extra) + else: + raise ValueError(f"Unknown user subcommand: {args.subcommand}") diff --git a/compiler_admin/commands/convert.py b/compiler_admin/commands/user/convert.py similarity index 100% rename from compiler_admin/commands/convert.py rename to compiler_admin/commands/user/convert.py diff --git a/compiler_admin/commands/create.py b/compiler_admin/commands/user/create.py similarity index 100% rename from compiler_admin/commands/create.py rename to compiler_admin/commands/user/create.py diff --git a/compiler_admin/commands/delete.py b/compiler_admin/commands/user/delete.py similarity index 100% rename from compiler_admin/commands/delete.py rename to compiler_admin/commands/user/delete.py diff --git a/compiler_admin/commands/offboard.py b/compiler_admin/commands/user/offboard.py similarity index 95% rename from compiler_admin/commands/offboard.py rename to compiler_admin/commands/user/offboard.py index 7c473cd..86682ec 100644 --- a/compiler_admin/commands/offboard.py +++ b/compiler_admin/commands/user/offboard.py @@ -2,8 +2,8 @@ from tempfile import NamedTemporaryFile from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE -from compiler_admin.commands.delete import delete -from compiler_admin.commands.signout import signout +from compiler_admin.commands.user.delete import delete +from compiler_admin.commands.user.signout import signout from compiler_admin.services.google import ( USER_ARCHIVE, CallGAMCommand, diff --git a/compiler_admin/commands/reset_password.py b/compiler_admin/commands/user/reset_password.py similarity index 95% rename from compiler_admin/commands/reset_password.py rename to compiler_admin/commands/user/reset_password.py index 023df31..7fb9bf1 100644 --- a/compiler_admin/commands/reset_password.py +++ b/compiler_admin/commands/user/reset_password.py @@ -1,7 +1,7 @@ from argparse import Namespace from compiler_admin import RESULT_SUCCESS, RESULT_FAILURE -from compiler_admin.commands.signout import signout +from compiler_admin.commands.user.signout import signout from compiler_admin.services.google import USER_HELLO, CallGAMCommand, user_account_name, user_exists diff --git a/compiler_admin/commands/restore.py b/compiler_admin/commands/user/restore.py similarity index 100% rename from compiler_admin/commands/restore.py rename to compiler_admin/commands/user/restore.py diff --git a/compiler_admin/commands/signout.py b/compiler_admin/commands/user/signout.py similarity index 100% rename from compiler_admin/commands/signout.py rename to compiler_admin/commands/user/signout.py diff --git a/compiler_admin/main.py b/compiler_admin/main.py index 3cb9cdc..3d8f500 100644 --- a/compiler_admin/main.py +++ b/compiler_admin/main.py @@ -1,21 +1,31 @@ -import argparse +from argparse import ArgumentParser, _SubParsersAction import sys from compiler_admin import __version__ as version -from compiler_admin.commands.create import create -from compiler_admin.commands.convert import ACCOUNT_TYPE_OU, convert -from compiler_admin.commands.delete import delete from compiler_admin.commands.info import info from compiler_admin.commands.init import init -from compiler_admin.commands.offboard import offboard -from compiler_admin.commands.reset_password import reset_password -from compiler_admin.commands.restore import restore -from compiler_admin.commands.signout import signout +from compiler_admin.commands.user import user +from compiler_admin.commands.user.convert import ACCOUNT_TYPE_OU + + +def add_sub_cmd(cmd: _SubParsersAction, subcmd, help) -> ArgumentParser: + """Helper creates a new subcommand parser.""" + return cmd.add_parser(subcmd, help=help) + + +def add_sub_cmd_username(cmd: _SubParsersAction, subcmd, help) -> ArgumentParser: + """Helper creates a new subcommand parser with a required username arg.""" + return add_username_arg(add_sub_cmd(cmd, subcmd, help=help)) + + +def add_username_arg(cmd: ArgumentParser) -> ArgumentParser: + cmd.add_argument("username", help="A Compiler user account name, sans domain.") + return cmd def main(argv=None): argv = argv if argv is not None else sys.argv[1:] - parser = argparse.ArgumentParser(prog="compiler-admin") + parser = ArgumentParser(prog="compiler-admin") # https://stackoverflow.com/a/8521644/812183 parser.add_argument( @@ -25,76 +35,56 @@ def main(argv=None): version=f"%(prog)s {version}", ) - subparsers = parser.add_subparsers(dest="command") + cmd_parsers = parser.add_subparsers(dest="command", help="The command to run") - def _subcmd(name, help, add_username_arg=True) -> argparse.ArgumentParser: - """Helper creates a new subcommand parser.""" - parser = subparsers.add_parser(name, help=help) - if add_username_arg is True: - parser.add_argument("username", help="A Compiler user account name, sans domain.") - return parser + info_cmd = add_sub_cmd(cmd_parsers, "info", help="Print configuration and debugging information.") + info_cmd.set_defaults(func=info) - _subcmd("info", help="Print configuration and debugging information.", add_username_arg=False) - - init_parser = _subcmd( - "init", - help="Initialize a new admin project. This command should be run once before any others.", + init_cmd = add_sub_cmd_username( + cmd_parsers, "init", help="Initialize a new admin project. This command should be run once before any others." ) - init_parser.add_argument("--gam", action="store_true", help="If provided, initialize a new GAM project.") - init_parser.add_argument("--gyb", action="store_true", help="If provided, initialize a new GYB project.") + init_cmd.add_argument("--gam", action="store_true", help="If provided, initialize a new GAM project.") + init_cmd.add_argument("--gyb", action="store_true", help="If provided, initialize a new GYB project.") + init_cmd.set_defaults(func=init) - create_parser = _subcmd("create", help="Create a new user in the Compiler domain.") - create_parser.add_argument("--notify", help="An email address to send the newly created account info.") + user_cmd = add_sub_cmd(cmd_parsers, "user", help="Work with users in the Compiler org.") + user_cmd.set_defaults(func=user) + user_subcmds = user_cmd.add_subparsers(dest="subcommand", help="The user command to run.") - convert_parser = _subcmd("convert", help="Convert a user account to a new type.") - convert_parser.add_argument( - "account_type", choices=ACCOUNT_TYPE_OU.keys(), help="Target account type for this conversion." - ) + user_create = add_sub_cmd_username(user_subcmds, "create", help="Create a new user in the Compiler domain.") + user_create.add_argument("--notify", help="An email address to send the newly created account info.") - 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." - ) + user_convert = add_sub_cmd_username(user_subcmds, "convert", help="Convert a user account to a new type.") + user_convert.add_argument("account_type", choices=ACCOUNT_TYPE_OU.keys(), help="Target account type for this conversion.") - 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( + user_delete = add_sub_cmd_username(user_subcmds, "delete", help="Delete a user account.") + user_delete.add_argument("--force", action="store_true", default=False, help="Don't ask for confirmation before deletion.") + + user_offboard = add_sub_cmd_username(user_subcmds, "offboard", help="Offboard a user account.") + user_offboard.add_argument("--alias", help="Account to assign username as an alias.") + user_offboard.add_argument( "--force", action="store_true", default=False, help="Don't ask for confirmation before offboarding." ) - reset_parser = _subcmd("reset-password", help="Reset a user's password to a randomly generated string.") - reset_parser.add_argument("--notify", help="An email address to send the newly generated password.") + user_reset = add_sub_cmd_username( + user_subcmds, "reset-password", help="Reset a user's password to a randomly generated string." + ) + user_reset.add_argument("--notify", help="An email address to send the newly generated password.") - _subcmd("restore", help="Restore an email backup from a prior offboarding.") + add_sub_cmd_username(user_subcmds, "restore", help="Restore an email backup from a prior offboarding.") - 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." - ) + user_signout = add_sub_cmd_username(user_subcmds, "signout", help="Signs a user out from all active sessions.") + user_signout.add_argument("--force", action="store_true", default=False, help="Don't ask for confirmation before signout.") if len(argv) == 0: argv = ["info"] args, extra = parser.parse_known_args(argv) - if args.command == "info": - return info() - elif args.command == "create": - return create(args, *extra) - elif args.command == "convert": - return convert(args) - elif args.command == "delete": - return delete(args) - elif args.command == "init": - return init(args) - elif args.command == "offboard": - return offboard(args) - elif args.command == "restore": - return restore(args) - elif args.command == "reset-password": - return reset_password(args) - elif args.command == "signout": - return signout(args) + if args.func: + return args.func(args, *extra) + else: + raise ValueError("Unrecognized command") if __name__ == "__main__": diff --git a/tests/commands/test_info.py b/tests/commands/test_info.py index 3887c36..4f9f3f7 100644 --- a/tests/commands/test_info.py +++ b/tests/commands/test_info.py @@ -1,6 +1,6 @@ import pytest -from compiler_admin import __version__ as version, RESULT_SUCCESS +from compiler_admin import RESULT_SUCCESS from compiler_admin.commands.info import info, __name__ as MODULE from compiler_admin.services.google import DOMAIN @@ -28,7 +28,7 @@ def test_info_e2e(capfd): captured = capfd.readouterr() assert res == RESULT_SUCCESS - assert f"compiler-admin: {version}" in captured.out + assert "compiler-admin:" in captured.out assert "GAMADV-XTD3" in captured.out assert f"Primary Domain: {DOMAIN}" in captured.out assert "Got Your Back" in captured.out diff --git a/tests/commands/test_convert.py b/tests/commands/user/test_convert.py similarity index 98% rename from tests/commands/test_convert.py rename to tests/commands/user/test_convert.py index 307715d..bac0c2c 100644 --- a/tests/commands/test_convert.py +++ b/tests/commands/user/test_convert.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.convert import convert, __name__ as MODULE +from compiler_admin.commands.user.convert import convert, __name__ as MODULE @pytest.fixture diff --git a/tests/commands/test_create.py b/tests/commands/user/test_create.py similarity index 97% rename from tests/commands/test_create.py rename to tests/commands/user/test_create.py index 285d54a..3d839a3 100644 --- a/tests/commands/test_create.py +++ b/tests/commands/user/test_create.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.create import create, __name__ as MODULE +from compiler_admin.commands.user.create import create, __name__ as MODULE from compiler_admin.services.google import USER_HELLO diff --git a/tests/commands/test_delete.py b/tests/commands/user/test_delete.py similarity index 96% rename from tests/commands/test_delete.py rename to tests/commands/user/test_delete.py index a714c14..143402a 100644 --- a/tests/commands/test_delete.py +++ b/tests/commands/user/test_delete.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.delete import delete, __name__ as MODULE +from compiler_admin.commands.user.delete import delete, __name__ as MODULE @pytest.fixture diff --git a/tests/commands/test_offboard.py b/tests/commands/user/test_offboard.py similarity index 97% rename from tests/commands/test_offboard.py rename to tests/commands/user/test_offboard.py index 08de141..56caf81 100644 --- a/tests/commands/test_offboard.py +++ b/tests/commands/user/test_offboard.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.offboard import offboard, __name__ as MODULE +from compiler_admin.commands.user.offboard import offboard, __name__ as MODULE @pytest.fixture diff --git a/tests/commands/test_reset_password.py b/tests/commands/user/test_reset_password.py similarity index 95% rename from tests/commands/test_reset_password.py rename to tests/commands/user/test_reset_password.py index 5d56269..58ee1d6 100644 --- a/tests/commands/test_reset_password.py +++ b/tests/commands/user/test_reset_password.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.reset_password import reset_password, __name__ as MODULE +from compiler_admin.commands.user.reset_password import reset_password, __name__ as MODULE from compiler_admin.services.google import USER_HELLO diff --git a/tests/commands/test_restore.py b/tests/commands/user/test_restore.py similarity index 93% rename from tests/commands/test_restore.py rename to tests/commands/user/test_restore.py index cfd05dd..05366c4 100644 --- a/tests/commands/test_restore.py +++ b/tests/commands/user/test_restore.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.restore import restore, __name__ as MODULE +from compiler_admin.commands.user.restore import restore, __name__ as MODULE @pytest.fixture diff --git a/tests/commands/test_signout.py b/tests/commands/user/test_signout.py similarity index 95% rename from tests/commands/test_signout.py rename to tests/commands/user/test_signout.py index 06419d0..472a1dd 100644 --- a/tests/commands/test_signout.py +++ b/tests/commands/user/test_signout.py @@ -2,7 +2,7 @@ import pytest from compiler_admin import RESULT_FAILURE, RESULT_SUCCESS -from compiler_admin.commands.signout import signout, __name__ as MODULE +from compiler_admin.commands.user.signout import signout, __name__ as MODULE @pytest.fixture diff --git a/tests/conftest.py b/tests/conftest.py index 549aeae..fa10dff 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -30,58 +30,64 @@ def mock_input(mock_module_name): @pytest.fixture def mock_commands_create(mock_module_name): - """Fixture returns a function that patches commands.create in a given module.""" + """Fixture returns a function that patches the create function in a given module.""" return mock_module_name("create") @pytest.fixture def mock_commands_convert(mock_module_name): - """Fixture returns a function that patches commands.convert in a given module.""" + """Fixture returns a function that patches the convert command function in a given module.""" return mock_module_name("convert") @pytest.fixture def mock_commands_delete(mock_module_name): - """Fixture returns a function that patches commands.delete in a given module.""" + """Fixture returns a function that patches the delete command function in a given module.""" return mock_module_name("delete") @pytest.fixture def mock_commands_info(mock_module_name): - """Fixture returns a function that patches commands.info in a given module.""" + """Fixture returns a function that patches the info command function in a given module.""" return mock_module_name("info") @pytest.fixture def mock_commands_init(mock_module_name): - """Fixture returns a function that patches commands.init in a given module.""" + """Fixture returns a function that patches the init command function in a given module.""" return mock_module_name("init") @pytest.fixture def mock_commands_offboard(mock_module_name): - """Fixture returns a function that patches commands.offboard in a given module.""" + """Fixture returns a function that patches the offboard command function in a given module.""" return mock_module_name("offboard") @pytest.fixture def mock_commands_reset_password(mock_module_name): - """Fixture returns a function that patches commands.reset_password in a given module.""" + """Fixture returns a function that patches the reset_password command function in a given module.""" return mock_module_name("reset_password") @pytest.fixture def mock_commands_restore(mock_module_name): - """Fixture returns a function that patches commands.restore in a given module.""" + """Fixture returns a function that patches the restore command function in a given module.""" return mock_module_name("restore") @pytest.fixture def mock_commands_signout(mock_module_name): - """Fixture returns a function that patches commands.signout in a given module.""" + """Fixture returns a function that patches the signout command function in a given module.""" return mock_module_name("signout") +@pytest.fixture +def mock_commands_user(mock_module_name): + """Fixture returns a function that patches the user command function in a given module.""" + return mock_module_name("user") + + @pytest.fixture def mock_google_CallGAMCommand(mock_module_name): """Fixture returns a function that patches the CallGAMCommand function from a given module.""" diff --git a/tests/test_main.py b/tests/test_main.py index 56a51ea..3d64f03 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -3,27 +3,11 @@ import pytest -from compiler_admin import __version__ as version import compiler_admin.main from compiler_admin.main import main, __name__ as MODULE from compiler_admin.services.google import DOMAIN -@pytest.fixture -def mock_commands_create(mock_commands_create): - return mock_commands_create(MODULE) - - -@pytest.fixture -def mock_commands_convert(mock_commands_convert): - return mock_commands_convert(MODULE) - - -@pytest.fixture -def mock_commands_delete(mock_commands_delete): - return mock_commands_delete(MODULE) - - @pytest.fixture def mock_commands_info(mock_commands_info): return mock_commands_info(MODULE) @@ -35,227 +19,256 @@ def mock_commands_init(mock_commands_init): @pytest.fixture -def mock_commands_offboard(mock_commands_offboard): - return mock_commands_offboard(MODULE) +def mock_commands_user(mock_commands_user): + return mock_commands_user(MODULE) -@pytest.fixture -def mock_commands_reset_password(mock_commands_reset_password): - return mock_commands_reset_password(MODULE) +def test_main_info(mock_commands_info): + main(argv=["info"]) + mock_commands_info.assert_called_once() -@pytest.fixture -def mock_commands_restore(mock_commands_restore): - return mock_commands_restore(MODULE) +def test_main_info_default(mock_commands_info): + main(argv=[]) -@pytest.fixture -def mock_commands_signout(mock_commands_signout): - return mock_commands_signout(MODULE) + mock_commands_info.assert_called_once() -def test_main_create(mock_commands_create): - main(argv=["create", "username"]) +def test_main_init_default(mock_commands_init): + main(argv=["init", "username"]) - mock_commands_create.assert_called_once() - call_args = mock_commands_create.call_args.args - assert Namespace(command="create", username="username", notify=None) in call_args + mock_commands_init.assert_called_once() + call_args = mock_commands_init.call_args.args + assert Namespace(func=mock_commands_init, command="init", username="username", gam=False, gyb=False) in call_args -def test_main_create_notify(mock_commands_create): - main(argv=["create", "username", "--notify", "notification"]) +def test_main_init_gam(mock_commands_init): + main(argv=["init", "username", "--gam"]) - mock_commands_create.assert_called_once() - call_args = mock_commands_create.call_args.args - assert Namespace(command="create", username="username", notify="notification") in call_args + mock_commands_init.assert_called_once() + call_args = mock_commands_init.call_args.args + assert Namespace(func=mock_commands_init, command="init", username="username", gam=True, gyb=False) in call_args -def test_main_create_extras(mock_commands_create): - main(argv=["create", "username", "extra1", "extra2"]) +def test_main_init_gyb(mock_commands_init): + main(argv=["init", "username", "--gyb"]) - mock_commands_create.assert_called_once() - call_args = mock_commands_create.call_args.args - assert Namespace(command="create", username="username", notify=None) in call_args - assert "extra1" in call_args - assert "extra2" in call_args + mock_commands_init.assert_called_once() + call_args = mock_commands_init.call_args.args + assert Namespace(func=mock_commands_init, command="init", username="username", gam=False, gyb=True) in call_args -def test_main_create_no_username(mock_commands_create): +def test_main_init_no_username(mock_commands_init): with pytest.raises(SystemExit): - main(argv=["create"]) - assert mock_commands_create.call_count == 0 - + main(argv=["init"]) + assert mock_commands_init.call_count == 0 -def test_main_convert(mock_commands_convert): - main(argv=["convert", "username", "contractor"]) - mock_commands_convert.assert_called_once() - call_args = mock_commands_convert.call_args.args - assert Namespace(command="convert", username="username", account_type="contractor") in call_args +def test_main_user_create(mock_commands_user): + main(argv=["user", "create", "username"]) + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="create", username="username", notify=None) in call_args + ) -def test_main_convert_no_username(mock_commands_convert): - with pytest.raises(SystemExit): - main(argv=["convert"]) - assert mock_commands_convert.call_count == 0 +def test_main_user_create_notify(mock_commands_user): + main(argv=["user", "create", "username", "--notify", "notification"]) -def test_main_convert_bad_account_type(mock_commands_convert): - with pytest.raises(SystemExit): - main(argv=["convert", "username", "account_type"]) - assert mock_commands_convert.call_count == 0 - + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="create", username="username", notify="notification") + in call_args + ) -def test_main_delete(mock_commands_delete): - main(argv=["delete", "username"]) - mock_commands_delete.assert_called_once() - call_args = mock_commands_delete.call_args.args - assert Namespace(command="delete", username="username", force=False) in call_args +def test_main_user_create_extras(mock_commands_user): + main(argv=["user", "create", "username", "extra1", "extra2"]) - -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 + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="create", username="username", notify=None) in call_args + ) + assert "extra1" in call_args + assert "extra2" in call_args -def test_main_delete_no_username(mock_commands_delete): +def test_main_user_create_no_username(mock_commands_user): with pytest.raises(SystemExit): - main(argv=["delete"]) - assert mock_commands_delete.call_count == 0 + main(argv=["user", "create"]) + assert mock_commands_user.call_count == 0 -def test_main_info(mock_commands_info): - main(argv=["info"]) - - mock_commands_info.assert_called_once() +def test_main_user_convert(mock_commands_user): + main(argv=["user", "convert", "username", "contractor"]) + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace( + func=mock_commands_user, command="user", subcommand="convert", username="username", account_type="contractor" + ) + in call_args + ) -def test_main_info_default(mock_commands_info): - main(argv=[]) - - mock_commands_info.assert_called_once() +def test_main_user_convert_no_username(mock_commands_user): + with pytest.raises(SystemExit): + main(argv=["user", "convert"]) + assert mock_commands_user.call_count == 0 -def test_main_init_default(mock_commands_init): - main(argv=["init", "username"]) - mock_commands_init.assert_called_once() - call_args = mock_commands_init.call_args.args - assert Namespace(command="init", username="username", gam=False, gyb=False) in call_args +def test_main_user_convert_bad_account_type(mock_commands_user): + with pytest.raises(SystemExit): + main(argv=["user", "convert", "username", "account_type"]) + assert mock_commands_user.call_count == 0 -def test_main_init_gam(mock_commands_init): - main(argv=["init", "username", "--gam"]) +def test_main_user_delete(mock_commands_user): + main(argv=["user", "delete", "username"]) - mock_commands_init.assert_called_once() - call_args = mock_commands_init.call_args.args - assert Namespace(command="init", username="username", gam=True, gyb=False) in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="delete", username="username", force=False) in call_args + ) -def test_main_init_gyb(mock_commands_init): - main(argv=["init", "username", "--gyb"]) +def test_main_user_delete_force(mock_commands_user): + main(argv=["user", "delete", "username", "--force"]) - mock_commands_init.assert_called_once() - call_args = mock_commands_init.call_args.args - assert Namespace(command="init", username="username", gam=False, gyb=True) in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="delete", username="username", force=True) in call_args + ) -def test_main_init_no_username(mock_commands_init): +def test_main_user_delete_no_username(mock_commands_user): with pytest.raises(SystemExit): - main(argv=["init"]) - assert mock_commands_init.call_count == 0 + main(argv=["user", "delete"]) + assert mock_commands_user.call_count == 0 -def test_main_offboard(mock_commands_offboard): - main(argv=["offboard", "username"]) +def test_main_user_offboard(mock_commands_user): + main(argv=["user", "offboard", "username"]) - mock_commands_offboard.assert_called_once() - call_args = mock_commands_offboard.call_args.args - assert Namespace(command="offboard", username="username", alias=None, force=False) in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="offboard", username="username", alias=None, force=False) + in call_args + ) -def test_main_offboard_force(mock_commands_offboard): - main(argv=["offboard", "username", "--force"]) +def test_main_user_offboard_force(mock_commands_user): + main(argv=["user", "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 + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="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"]) +def test_main_user_offboard_with_alias(mock_commands_user): + main(argv=["user", "offboard", "username", "--alias", "anotheruser"]) - mock_commands_offboard.assert_called_once() - call_args = mock_commands_offboard.call_args.args - assert Namespace(command="offboard", username="username", alias="anotheruser", force=False) in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace( + func=mock_commands_user, + command="user", + subcommand="offboard", + username="username", + alias="anotheruser", + force=False, + ) + in call_args + ) -def test_main_offboard_no_username(mock_commands_offboard): +def test_main_user_offboard_no_username(mock_commands_user): with pytest.raises(SystemExit): - main(argv=["offboard"]) - assert mock_commands_offboard.call_count == 0 + main(argv=["user", "offboard"]) + assert mock_commands_user.call_count == 0 -def test_main_reset_password(mock_commands_reset_password): - main(argv=["reset-password", "username"]) +def test_main_user_reset_password(mock_commands_user): + main(argv=["user", "reset-password", "username"]) - mock_commands_reset_password.assert_called_once() - call_args = mock_commands_reset_password.call_args.args - assert Namespace(command="reset-password", username="username", notify=None) in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="reset-password", username="username", notify=None) + in call_args + ) -def test_main_reset_password_notify(mock_commands_reset_password): - main(argv=["reset-password", "username", "--notify", "notification"]) +def test_main_user_reset_password_notify(mock_commands_user): + main(argv=["user", "reset-password", "username", "--notify", "notification"]) - mock_commands_reset_password.assert_called_once() - call_args = mock_commands_reset_password.call_args.args - assert Namespace(command="reset-password", username="username", notify="notification") in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace( + func=mock_commands_user, command="user", subcommand="reset-password", username="username", notify="notification" + ) + in call_args + ) -def test_main_reset_password_no_username(mock_commands_reset_password): +def test_main_user_reset_password_no_username(mock_commands_user): with pytest.raises(SystemExit): - main(argv=["reset-password"]) - assert mock_commands_reset_password.call_count == 0 + main(argv=["user", "reset-password"]) + assert mock_commands_user.call_count == 0 -def test_main_restore(mock_commands_restore): - main(argv=["restore", "username"]) +def test_main_user_restore(mock_commands_user): + main(argv=["user", "restore", "username"]) - mock_commands_restore.assert_called_once() - call_args = mock_commands_restore.call_args.args - assert Namespace(command="restore", username="username") in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert Namespace(func=mock_commands_user, command="user", subcommand="restore", username="username") in call_args -def test_main_restore_no_username(mock_commands_restore): +def test_main_user_restore_no_username(mock_commands_user): with pytest.raises(SystemExit): - main(argv=["restore"]) - assert mock_commands_restore.call_count == 0 + main(argv=["user", "restore"]) + assert mock_commands_user.call_count == 0 -def test_main_signout(mock_commands_signout): - main(argv=["signout", "username"]) +def test_main_user_signout(mock_commands_user): + main(argv=["user", "signout", "username"]) - mock_commands_signout.assert_called_once() - call_args = mock_commands_signout.call_args.args - assert Namespace(command="signout", username="username", force=False) in call_args + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="signout", username="username", force=False) in call_args + ) -def test_main_signout_force(mock_commands_signout): - main(argv=["signout", "username", "--force"]) +def test_main_user_signout_force(mock_commands_user): + main(argv=["user", "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 + mock_commands_user.assert_called_once() + call_args = mock_commands_user.call_args.args + assert ( + Namespace(func=mock_commands_user, command="user", subcommand="signout", username="username", force=True) in call_args + ) -def test_main_signout_no_username(mock_commands_signout): +def test_main_user_signout_no_username(mock_commands_user): with pytest.raises(SystemExit): - main(argv=["signout"]) - assert mock_commands_signout.call_count == 0 + main(argv=["user", "signout"]) + assert mock_commands_user.call_count == 0 @pytest.mark.e2e @@ -274,7 +287,7 @@ def test_run_compiler_admin(capfd): captured = capfd.readouterr() assert res == 0 - assert f"compiler-admin: {version}" in captured.out + assert "compiler-admin:" in captured.out assert "GAMADV-XTD3" in captured.out assert f"Primary Domain: {DOMAIN}" in captured.out assert "WARNING: Config File:" not in captured.err