def test_already_attached(self, _m_getuid, capsys, FakeConfig, event): """Check that an already-attached machine emits message and exits 0""" account_name = "test_account" cfg = FakeConfig.for_attached_machine(account_name=account_name) with pytest.raises(AlreadyAttachedError): action_attach(mock.MagicMock(), cfg=cfg) with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): main_error_handler(action_attach)(mock.MagicMock(), cfg) msg = messages.ALREADY_ATTACHED.format(account_name=account_name) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": msg.msg, "message_code": msg.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
def test_non_root_users_are_rejected(getuid, FakeConfig, capsys, event): """Check that a UID != 0 will receive a message and exit non-zero""" getuid.return_value = 1 cfg = FakeConfig() with pytest.raises(NonRootUserError): action_attach(mock.MagicMock(), cfg) with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): main_error_handler(action_attach)(mock.MagicMock(), cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": messages.NONROOT_USER.msg, "message_code": messages.NONROOT_USER.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
def test_already_attached(self, _m_getuid, capsys, FakeConfig): """Check that an already-attached machine emits message and exits 0""" account_name = "test_account" cfg = FakeConfig.for_attached_machine(account_name=account_name) with pytest.raises(AlreadyAttachedError): action_attach(mock.MagicMock(), cfg)
def test_non_root_users_are_rejected(getuid, FakeConfig): """Check that a UID != 0 will receive a message and exit non-zero""" getuid.return_value = 1 cfg = FakeConfig() with pytest.raises(NonRootUserError): action_attach(mock.MagicMock(), cfg)
def test_token_is_a_required_argument(self, _m_getuid, FakeConfig): """When missing the required token argument, raise a UserFacingError""" args = mock.MagicMock() args.token = None with pytest.raises(UserFacingError) as e: action_attach(args, FakeConfig()) assert status.MESSAGE_ATTACH_REQUIRES_TOKEN == str(e.value)
def test_attach_config_enable_services( self, _m_daemon_stop, m_status, m_format_tabular, m_handle_unicode, m_attach_with_token, m_enable, _m_getuid, auto_enable, FakeConfig, event, ): m_status.return_value = ("status", 0) m_format_tabular.return_value = "status" m_handle_unicode.return_value = "status" cfg = FakeConfig() args = mock.MagicMock( token=None, attach_config=FakeFile( yaml.dump({"token": "faketoken", "enable_services": ["cis"]}) ), auto_enable=auto_enable, ) action_attach(args, cfg=cfg) assert [ mock.call(mock.ANY, token="faketoken", allow_enable=False) ] == m_attach_with_token.call_args_list if auto_enable: assert [ mock.call(cfg, "cis", assume_yes=True, allow_beta=True) ] == m_enable.call_args_list else: assert [] == m_enable.call_args_list args.attach_config = FakeFile( yaml.dump({"token": "faketoken", "enable_services": ["cis"]}) ) fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): main_error_handler(action_attach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "success", "errors": [], "failed_services": [], "needs_reboot": False, "processed_services": ["cis"] if auto_enable else [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_attach_config_and_token_mutually_exclusive( self, _m_getuid, FakeConfig ): args = mock.MagicMock( token="something", attach_config=FakeFile("something") ) cfg = FakeConfig() with pytest.raises(UserFacingError) as e: action_attach(args, cfg=cfg) assert e.value.msg == messages.ATTACH_TOKEN_ARG_XOR_CONFIG.msg
def test_lock_file_exists(self, m_subp, _m_getuid, capsys, FakeConfig): """Check when an operation holds a lock file, attach cannot run.""" cfg = FakeConfig() cfg.write_cache("lock", "123:ua disable") with pytest.raises(LockHeldError) as exc_info: action_attach(mock.MagicMock(), cfg) assert [mock.call(["ps", "123"])] == m_subp.call_args_list assert ("Unable to perform: ua attach.\n" "Operation in progress: ua disable (pid:123)" ) == exc_info.value.msg
def test_token_from_attach_config( self, m_attach_with_token, _m_post_cli_attach, _m_getuid, FakeConfig ): args = mock.MagicMock( token=None, attach_config=FakeFile(yaml.dump({"token": "faketoken"})), ) cfg = FakeConfig() action_attach(args, cfg=cfg) assert [ mock.call(mock.ANY, token="faketoken", allow_enable=True) ] == m_attach_with_token.call_args_list
def test_attach_config_invalid_config( self, _m_getuid, FakeConfig, capsys, event ): args = mock.MagicMock( token=None, attach_config=FakeFile( yaml.dump({"token": "something", "enable_services": "cis"}), name="fakename", ), ) cfg = FakeConfig() with pytest.raises(UserFacingError) as e: action_attach(args, cfg=cfg) assert "Error while reading fakename: " in e.value.msg args.attach_config = FakeFile( yaml.dump({"token": "something", "enable_services": "cis"}), name="fakename", ) with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): main_error_handler(action_attach)(args, cfg) expected_message = messages.ATTACH_CONFIG_READ_ERROR.format( config_name="fakename", error=( "Got value with " 'incorrect type for field\n"enable_services": ' "Expected value with type list but got value: 'cis'" ), ) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_message.msg, "message_code": expected_message.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
def test_auto_enable_passed_through_to_request_updated_contract( self, _m_getuid, auto_enable): args = mock.MagicMock(auto_enable=auto_enable) def fake_contract_updates(cfg, contract_token, allow_enable): cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN) return True with mock.patch(M_PATH + "contract.request_updated_contract") as m_ruc: m_ruc.side_effect = fake_contract_updates action_attach(args, FakeConfig()) expected_call = mock.call(mock.ANY, mock.ANY, allow_enable=auto_enable) assert [expected_call] == m_ruc.call_args_list
def test_happy_path_without_token_arg(self, action_status, contract_client, discharge_root_macaroon, request_updated_contract): """A mock-heavy test for the happy path without an argument""" # TODO: Improve this test with less general mocking and more # post-conditions bound_macaroon = b'bound_bytes_macaroon' discharge_root_macaroon.return_value = bound_macaroon args = mock.MagicMock(token=None) cfg = FakeConfig.with_account() machine_token = { 'machineTokenInfo': { 'contractInfo': { 'name': 'mycontract', 'resourceEntitlements': [] } } } def fake_contract_updates(cfg, contract_token, allow_enable): cfg.write_cache('machine-token', machine_token) return True request_updated_contract.side_effect = fake_contract_updates ret = action_attach(args, cfg) assert 0 == ret assert 1 == action_status.call_count expected_macaroon = bound_macaroon.decode('utf-8') assert expected_macaroon == cfg._cache_contents['bound-macaroon']
def test_status_updated_when_auto_enable_fails( self, request_updated_contract, _m_get_available_resources, _m_get_uid, error_class, error_str, expected_log, caplog_text, FakeConfig, ): """If auto-enable of a service fails, attach status is updated.""" token = "contract-token" args = mock.MagicMock(token=token) cfg = FakeConfig() cfg.status() # persist unattached status # read persisted status cache from disk orig_unattached_status = cfg.read_cache("status-cache") def fake_request_updated_contract(cfg, contract_token, allow_enable): cfg.write_cache("machine-token", ENTITLED_MACHINE_TOKEN) raise error_class(error_str) request_updated_contract.side_effect = fake_request_updated_contract ret = action_attach(args, cfg) assert 1 == ret assert cfg.is_attached # Assert updated status cache is written to disk assert orig_unattached_status != cfg.read_cache( "status-cache" ), "Did not persist on disk status during attach failure" logs = caplog_text() assert expected_log in logs
def test_happy_path_with_token_arg(self, action_status, contract_machine_attach, discharge_root_macaroon): """A mock-heavy test for the happy path with the contract token arg""" # TODO: Improve this test with less general mocking and more # post-conditions token = 'contract-token' args = mock.MagicMock(token=token) cfg = FakeConfig.with_account() machine_token = { 'machineTokenInfo': { 'contractInfo': { 'name': 'mycontract', 'resourceEntitlements': [] } } } def fake_contract_attach(contract_token): cfg.write_cache('machine-token', machine_token) return machine_token contract_machine_attach.side_effect = fake_contract_attach ret = action_attach(args, cfg) assert 0 == ret assert 1 == action_status.call_count expected_calls = [mock.call(contract_token=token)] assert expected_calls == contract_machine_attach.call_args_list assert 0 == discharge_root_macaroon.call_count
def test_lock_file_exists( self, m_subp, _m_getuid, capsys, FakeConfig, event ): """Check when an operation holds a lock file, attach cannot run.""" cfg = FakeConfig() cfg.write_cache("lock", "123:ua disable") with pytest.raises(LockHeldError) as exc_info: action_attach(mock.MagicMock(), cfg=cfg) assert [mock.call(["ps", "123"])] == m_subp.call_args_list assert ( "Unable to perform: ua attach.\n" "Operation in progress: ua disable (pid:123)" ) == exc_info.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): with mock.patch.object( cfg, "check_lock_info" ) as m_check_lock_info: m_check_lock_info.return_value = (1, "lock_holder") main_error_handler(action_attach)(mock.MagicMock(), cfg) expected_msg = messages.LOCK_HELD_ERROR.format( lock_request="ua attach", lock_holder="lock_holder", pid=1 ) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_msg.msg, "message_code": expected_msg.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
def test_non_root_users_are_rejected(stdout, getuid): """Check that a UID != 0 will receive a message and exit non-zero""" getuid.return_value = 1 cfg = TestConfig() ret = action_attach(mock.MagicMock(), cfg) assert 1 == ret assert (mock.call(status.MESSAGE_NONROOT_USER) in stdout.write.call_args_list)
def test_already_attached(self, stdout): """Check that an already-attached machine emits message and exits 0""" account_name = 'test_account' cfg = TestConfig.for_attached_machine(account_name=account_name) ret = action_attach(mock.MagicMock(), cfg) assert 0 == ret expected_msg = "This machine is already attached to '{}'.".format( account_name) assert mock.call(expected_msg) in stdout.write.call_args_list
def test_already_attached(self, _m_getuid, capsys): """Check that an already-attached machine emits message and exits 0""" account_name = "test_account" cfg = FakeConfig.for_attached_machine(account_name=account_name) ret = action_attach(mock.MagicMock(), cfg) assert 0 == ret expected_msg = "This machine is already attached to '{}'.".format( account_name) assert expected_msg in capsys.readouterr()[0]
def test_token_is_a_required_argument( self, _m_getuid, FakeConfig, capsys, event ): """When missing the required token argument, raise a UserFacingError""" args = mock.MagicMock(token=None, attach_config=None) cfg = FakeConfig() with pytest.raises(UserFacingError) as e: action_attach(args, cfg=cfg) assert messages.ATTACH_REQUIRES_TOKEN.msg == str(e.value.msg) args = mock.MagicMock() args.token = None args.attach_config = None with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): with mock.patch.object( cfg, "check_lock_info" ) as m_check_lock_info: m_check_lock_info.return_value = (0, "lock_holder") main_error_handler(action_attach)(args, cfg) expected_msg = messages.ATTACH_REQUIRES_TOKEN expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_msg.msg, "message_code": expected_msg.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
def test_no_discharged_macaroon(self, action_status, contract_client, discharge_root_macaroon, stdout): """If we can't discharge the root macaroon, fail gracefully.""" discharge_root_macaroon.return_value = None args = mock.MagicMock(token=None) cfg = TestConfig.with_account() ret = action_attach(args, cfg) assert 1 == ret expected_msg = ('Could not attach machine. Unable to obtain' ' authenticated user token') assert mock.call(expected_msg) in stdout.write.call_args_list
def test_auto_enable_passed_through_to_request_updated_contract( self, m_update_apt_and_motd_msgs, _m_get_available_resources, _m_should_reboot, _m_remove_notice, _m_get_uid, auto_enable, FakeConfig, ): args = mock.MagicMock(auto_enable=auto_enable, attach_config=None) def fake_contract_updates(cfg, contract_token, allow_enable): cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN) return True cfg = FakeConfig() with mock.patch(M_PATH + "contract.request_updated_contract") as m_ruc: m_ruc.side_effect = fake_contract_updates action_attach(args, cfg) expected_call = mock.call(mock.ANY, mock.ANY, allow_enable=auto_enable) assert [expected_call] == m_ruc.call_args_list assert [mock.call(cfg)] == m_update_apt_and_motd_msgs.call_args_list
def test_happy_path_without_token_arg(self, action_status, contract_client, discharge_root_macaroon): """A mock-heavy test for the happy path without an argument""" # TODO: Improve this test with less general mocking and more # post-conditions bound_macaroon = b'bound_bytes_macaroon' discharge_root_macaroon.return_value = bound_macaroon args = mock.MagicMock(token=None) cfg = TestConfig.with_account() ret = action_attach(args, cfg) assert 0 == ret assert 1 == action_status.call_count expected_macaroon = bound_macaroon.decode('utf-8') assert expected_macaroon == cfg._cache_contents['bound-macaroon']
def test_happy_path_with_token_arg(self, action_status, contract_machine_attach, discharge_root_macaroon): """A mock-heavy test for the happy path with the contract token arg""" # TODO: Improve this test with less general mocking and more # post-conditions token = 'contract-token' args = mock.MagicMock(token=token) cfg = TestConfig.with_account() ret = action_attach(args, cfg) assert 0 == ret assert 1 == action_status.call_count expected_calls = [mock.call(contract_token=token)] assert expected_calls == contract_machine_attach.call_args_list assert 0 == discharge_root_macaroon.call_count
def test_happy_path_with_token_arg(self, action_status, contract_machine_attach, _m_getuid): """A mock-heavy test for the happy path with the contract token arg""" # TODO: Improve this test with less general mocking and more # post-conditions token = "contract-token" args = mock.MagicMock(token=token) cfg = FakeConfig() def fake_contract_attach(contract_token): cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN) return BASIC_MACHINE_TOKEN contract_machine_attach.side_effect = fake_contract_attach ret = action_attach(args, cfg) assert 0 == ret assert 1 == action_status.call_count expected_calls = [mock.call(contract_token=token)] assert expected_calls == contract_machine_attach.call_args_list
def _run_ua_attach(cfg: UAConfig, token: str) -> bool: """Attach to a UA subscription with a given token. :return: True if attach performed without errors. """ import argparse from uaclient import cli print(colorize_commands([["ua", "attach", token]])) try: ret_code = cli.action_attach( argparse.Namespace( token=token, auto_enable=True, format="cli", attach_config=None ), cfg, ) return ret_code == 0 except exceptions.UserFacingError as err: print(err.msg) return False
def test_happy_path_with_token_arg( self, m_format_tabular, m_status, contract_machine_attach, m_update_apt_and_motd_msgs, _m_should_reboot, _m_remove_notice, _m_getuid, FakeConfig, event, ): """A mock-heavy test for the happy path with the contract token arg""" # TODO: Improve this test with less general mocking and more # post-conditions token = "contract-token" args = mock.MagicMock(token=token, attach_config=None) cfg = FakeConfig() def fake_contract_attach(contract_token): cfg.write_cache("machine-token", BASIC_MACHINE_TOKEN) return BASIC_MACHINE_TOKEN contract_machine_attach.side_effect = fake_contract_attach ret = action_attach(args, cfg) assert 0 == ret assert 1 == m_status.call_count assert 1 == m_format_tabular.call_count expected_calls = [mock.call(contract_token=token)] assert expected_calls == contract_machine_attach.call_args_list assert [mock.call(cfg)] == m_update_apt_and_motd_msgs.call_args_list # We need to do that since all config objects in this # test will share the same data dir. Since this will # test a successful attach, in the end we write a machine token # file, which will make all other cfg objects here to report # as attached cfg.delete_cache() cfg = FakeConfig() args = mock.MagicMock(token=token, attach_config=None) with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): with mock.patch.object( cfg, "check_lock_info" ) as m_check_lock_info: m_check_lock_info.return_value = (0, "lock_holder") fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_attach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "success", "errors": [], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())