def test_non_root_users_are_rejected(self, m_getuid, _m_prompt, FakeConfig, event, capsys): """Check that a UID != 0 will receive a message and exit non-zero""" m_getuid.return_value = 1 args = mock.MagicMock() cfg = FakeConfig.for_attached_machine() with pytest.raises(exceptions.NonRootUserError): action_detach(args, cfg=cfg) with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): main_error_handler(action_detach)(args, cfg) expected_message = messages.NONROOT_USER 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_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_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_unattached_invalid_and_valid_service_error_message( self, _request_updated_contract, m_getuid, uid, expected_error_template, event, FakeConfig, ): """Check invalid service name results in custom error message.""" m_getuid.return_value = uid cfg = FakeConfig() args = mock.MagicMock() args.service = ["bogus", "fips"] args.command = "enable" with pytest.raises(exceptions.UserFacingError) as err: action_enable(args, cfg) if not uid: expected_error = expected_error_template.format( operation="enable", valid_service="fips", invalid_service="bogus", service_msg="", ) else: expected_error = expected_error_template assert expected_error.msg == err.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_enable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_lock_file_exists( self, m_subp, _request_updated_contract, getuid, capsys, event, FakeConfig, ): """Check inability to enable if operation holds lock file.""" getuid.return_value = 0 cfg = FakeConfig.for_attached_machine() cfg.write_cache("lock", "123:ua disable") args = mock.MagicMock() with pytest.raises(exceptions.LockHeldError) as err: action_enable(args, cfg=cfg) assert [mock.call(["ps", "123"])] == m_subp.call_args_list expected_message = messages.LOCK_HELD_ERROR.format( lock_request="ua enable", lock_holder="ua disable", pid="123" ) assert expected_message.msg == err.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_enable)(args, cfg) expected_msg = messages.LOCK_HELD_ERROR.format( lock_request="ua enable", 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_unattached_error_message( self, _request_updated_contract, m_getuid, uid, expected_error_template, capsys, event, FakeConfig, ): """Check that root user gets unattached message.""" m_getuid.return_value = uid cfg = FakeConfig() args = mock.MagicMock() args.command = "enable" args.service = ["esm-infra"] if not uid: expected_error = expected_error_template.format( valid_service="esm-infra" ) else: expected_error = expected_error_template with pytest.raises(exceptions.UserFacingError) as err: action_enable(args, cfg) assert expected_error.msg == err.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): main_error_handler(action_enable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", } ], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
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_informational_message_emitted( self, m_update_apt_and_motd_msgs, m_client, m_entitlements, m_getuid, _m_prompt, capsys, classes, expected_message, disabled_services, FakeConfig, tmpdir, event, ): m_getuid.return_value = 0 m_entitlements.ENTITLEMENT_CLASSES = classes fake_client = FakeContractClient(FakeConfig.for_attached_machine()) m_client.return_value = fake_client m_cfg = mock.MagicMock() m_cfg.check_lock_info.return_value = (-1, "") m_cfg.data_path.return_value = tmpdir.join("lock").strpath args = mock.MagicMock() action_detach(args, m_cfg) out, _err = capsys.readouterr() assert expected_message in out assert [mock.call(m_cfg)] == m_update_apt_and_motd_msgs.call_args_list cfg = FakeConfig.for_attached_machine() 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_detach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "success", "errors": [], "failed_services": [], "needs_reboot": False, "processed_services": disabled_services, "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_unattached_error_message(self, m_getuid, uid, expected_error_template, FakeConfig, event): """Check that root user gets unattached message.""" m_getuid.return_value = uid cfg = FakeConfig() args = mock.MagicMock() args.command = "disable" if not uid: expected_error = expected_error_template.format( valid_service="esm-infra") else: expected_error = expected_error_template with pytest.raises(exceptions.UserFacingError) as err: args.service = ["esm-infra"] action_disable(args, cfg) assert expected_error.msg == err.value.msg args.assume_yes = True args.format = "json" with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): with mock.patch.object(event, "set_event_mode"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_disable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [{ "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", }], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_invalid_service_names(self, m_getuid, service, FakeConfig, event): m_getuid.return_value = 0 expected_error_tmpl = messages.INVALID_SERVICE_OP_FAILURE cfg = FakeConfig.for_attached_machine() args = mock.MagicMock() expected_error = expected_error_tmpl.format( operation="disable", invalid_service=", ".join(sorted(service)), service_msg=ALL_SERVICE_MSG, ) with pytest.raises(exceptions.UserFacingError) as err: args.service = service action_disable(args, cfg) assert expected_error.msg == err.value.msg args.assume_yes = True args.format = "json" with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): with mock.patch.object(event, "set_event_mode"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_disable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [{ "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", }], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
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_lock_file_exists(self, m_subp, m_getuid, FakeConfig, event): """Check inability to disable if operation in progress holds lock.""" cfg = FakeConfig().for_attached_machine() args = mock.MagicMock() expected_error = messages.LOCK_HELD_ERROR.format( lock_request="ua disable", lock_holder="ua enable", pid="123") with open(cfg.data_path("lock"), "w") as stream: stream.write("123:ua enable") with pytest.raises(exceptions.LockHeldError) as err: args.service = ["esm-infra"] action_disable(args, cfg) assert [mock.call(["ps", "123"])] == m_subp.call_args_list assert expected_error.msg == err.value.msg args.assume_yes = True args.format = "json" with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): with mock.patch.object(event, "set_event_mode"): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_disable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [{ "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", }], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
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_lock_file_exists(self, m_subp, m_getuid, m_prompt, FakeConfig, capsys, event): """Check when an operation holds a lock file, detach cannot run.""" m_getuid.return_value = 0 cfg = FakeConfig.for_attached_machine() args = mock.MagicMock() with open(cfg.data_path("lock"), "w") as stream: stream.write("123:ua enable") with pytest.raises(exceptions.LockHeldError) as err: action_detach(args, cfg=cfg) assert [mock.call(["ps", "123"])] == m_subp.call_args_list expected_error_msg = messages.LOCK_HELD_ERROR.format( lock_request="ua detach", lock_holder="ua enable", pid="123") assert expected_error_msg.msg == err.value.msg with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): main_error_handler(action_detach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [{ "message": expected_error_msg.msg, "message_code": expected_error_msg.name, "service": None, "type": "system", }], "failed_services": [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(capsys.readouterr()[0])
def test_status_updated_when_auto_enable_fails( self, request_updated_contract, m_update_apt_and_motd_msgs, _m_get_available_resources, _m_should_reboot, _m_remove_notice, _m_get_uid, error_class, error_str, FakeConfig, event, ): """If auto-enable of a service fails, attach status is updated.""" token = "contract-token" args = mock.MagicMock(token=token, attach_config=None) cfg = FakeConfig() status.status(cfg=cfg) # 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 with pytest.raises(SystemExit) as excinfo: main_error_handler(action_attach)(args, cfg) assert 1 == excinfo.value.code 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" assert [mock.call(cfg)] == m_update_apt_and_motd_msgs.call_args_list
def test_entitlements_not_found_disabled_and_enabled( self, m_status, m_valid_services, m_entitlement_factory, _m_getuid, assume_yes, tmpdir, event, FakeConfig, ): expected_error_tmpl = messages.INVALID_SERVICE_OP_FAILURE num_calls = 2 m_ent1_cls = mock.Mock() m_ent1_obj = m_ent1_cls.return_value m_ent1_obj.disable.return_value = ( False, CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=messages.NamedMessage("test-code", "test"), ), ) type(m_ent1_obj).name = mock.PropertyMock(return_value="ent1") m_ent2_cls = mock.Mock() m_ent2_obj = m_ent2_cls.return_value m_ent2_obj.disable.return_value = ( False, CanDisableFailure( CanDisableFailureReason.ALREADY_DISABLED, message=messages.NamedMessage("test-code2", "test2"), ), ) type(m_ent2_obj).name = mock.PropertyMock(return_value="ent2") m_ent3_cls = mock.Mock() m_ent3_obj = m_ent3_cls.return_value m_ent3_obj.disable.return_value = (True, None) type(m_ent3_obj).name = mock.PropertyMock(return_value="ent3") def factory_side_effect(cfg, name): if name == "ent2": return m_ent2_cls if name == "ent3": return m_ent3_cls return None m_entitlement_factory.side_effect = factory_side_effect m_valid_services.return_value = ["ent2", "ent3"] cfg = FakeConfig.for_attached_machine() args_mock = mock.Mock() args_mock.service = ["ent1", "ent2", "ent3"] args_mock.assume_yes = assume_yes with pytest.raises(exceptions.UserFacingError) as err: with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): action_disable(args_mock, cfg=cfg) assert (expected_error_tmpl.format( operation="disable", invalid_service="ent1", service_msg="Try ent2, ent3.", ).msg == err.value.msg) for m_ent_cls in [m_ent2_cls, m_ent3_cls]: assert [mock.call(cfg, assume_yes=assume_yes) ] == m_ent_cls.call_args_list expected_disable_call = mock.call() for m_ent in [m_ent2_obj, m_ent3_obj]: assert [expected_disable_call] == m_ent.disable.call_args_list assert 0 == m_ent1_obj.call_count assert num_calls == m_status.call_count cfg = FakeConfig.for_attached_machine() args_mock.assume_yes = True args_mock.format = "json" with pytest.raises(SystemExit): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): with mock.patch.object(event, "set_event_mode"): with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_disable)(args_mock, cfg=cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": "test2", "message_code": "test-code2", "service": "ent2", "type": "service", }, { "message": ("Cannot disable unknown service 'ent1'.\n" "Try ent2, ent3."), "message_code": "invalid-service-or-failure", "service": None, "type": "system", }, ], "failed_services": ["ent2"], "needs_reboot": False, "processed_services": ["ent3"], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_invalid_service_names( self, _m_request_updated_contract, m_getuid, service, beta, event, FakeConfig, ): m_getuid.return_value = 0 expected_error_tmpl = messages.INVALID_SERVICE_OP_FAILURE expected_msg = "One moment, checking your subscription first\n" cfg = FakeConfig.for_attached_machine() args_mock = mock.MagicMock() args_mock.service = service args_mock.beta = beta with pytest.raises(exceptions.UserFacingError) as err: fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): action_enable(args_mock, cfg) assert expected_msg == fake_stdout.getvalue() service_names = entitlements.valid_services(cfg=cfg, allow_beta=beta) ent_str = "Try " + ", ".join(service_names) + "." service_msg = "\n".join( textwrap.wrap( ent_str, width=80, break_long_words=False, break_on_hyphens=False, ) ) expected_error = expected_error_tmpl.format( operation="enable", invalid_service=", ".join(sorted(service)), service_msg=service_msg, ) assert expected_error.msg == err.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_enable)(args_mock, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", } ], "failed_services": service, "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_entitlements_not_found_and_beta( self, m_valid_services, m_entitlement_factory, _m_get_available_resources, _m_request_updated_contract, m_getuid, beta_flag, event, FakeConfig, ): m_getuid.return_value = 0 expected_error_tmpl = messages.INVALID_SERVICE_OP_FAILURE m_ent1_cls = mock.Mock() m_ent1_obj = m_ent1_cls.return_value m_ent1_obj.enable.return_value = (False, None) m_ent2_cls = mock.Mock() m_ent2_cls.name = "ent2" m_ent2_is_beta = mock.PropertyMock(return_value=True) type(m_ent2_cls)._is_beta = m_ent2_is_beta m_ent2_obj = m_ent2_cls.return_value failure_reason = CanEnableFailure(CanEnableFailureReason.IS_BETA) if beta_flag: m_ent2_obj.enable.return_value = (True, None) else: m_ent2_obj.enable.return_value = (False, failure_reason) m_ent3_cls = mock.Mock() m_ent3_cls.name = "ent3" m_ent3_is_beta = mock.PropertyMock(return_value=False) type(m_ent3_cls)._is_beta = m_ent3_is_beta m_ent3_obj = m_ent3_cls.return_value m_ent3_obj.enable.return_value = (True, None) cfg = FakeConfig.for_attached_machine() assume_yes = False args_mock = mock.Mock() args_mock.service = ["ent1", "ent2", "ent3"] args_mock.assume_yes = assume_yes args_mock.beta = beta_flag def factory_side_effect(cfg, name, not_found_okay=True): if name == "ent2": return m_ent2_cls if name == "ent3": return m_ent3_cls return None m_entitlement_factory.side_effect = factory_side_effect def valid_services_side_effect(cfg, allow_beta, all_names=False): if allow_beta: return ["ent2", "ent3"] return ["ent2"] m_valid_services.side_effect = valid_services_side_effect expected_msg = "One moment, checking your subscription first\n" not_found_name = "ent1" mock_ent_list = [m_ent3_cls] mock_obj_list = [m_ent3_obj] service_names = entitlements.valid_services(cfg, allow_beta=beta_flag) ent_str = "Try " + ", ".join(service_names) + "." if not beta_flag: not_found_name += ", ent2" else: mock_ent_list.append(m_ent2_cls) mock_obj_list.append(m_ent3_obj) service_msg = "\n".join( textwrap.wrap( ent_str, width=80, break_long_words=False, break_on_hyphens=False, ) ) with pytest.raises(exceptions.UserFacingError) as err: fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): action_enable(args_mock, cfg) expected_error = expected_error_tmpl.format( operation="enable", invalid_service=not_found_name, service_msg=service_msg, ) assert expected_error.msg == err.value.msg assert expected_msg == fake_stdout.getvalue() for m_ent_cls in mock_ent_list: assert [ mock.call( cfg, assume_yes=assume_yes, allow_beta=beta_flag, called_name=m_ent_cls.name, ) ] == m_ent_cls.call_args_list expected_enable_call = mock.call() for m_ent in mock_obj_list: assert [expected_enable_call] == m_ent.enable.call_args_list assert 0 == m_ent1_obj.call_count event.reset() with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_enable)(args_mock, cfg=cfg) expected_failed_services = ["ent1", "ent2"] if beta_flag: expected_failed_services = ["ent1"] expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", } ], "failed_services": expected_failed_services, "needs_reboot": False, "processed_services": ["ent2", "ent3"] if beta_flag else ["ent3"], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_entitlements_not_found_disabled_and_enabled( self, m_valid_services, m_entitlement_factory, _m_get_available_resources, _m_request_updated_contract, m_getuid, event, FakeConfig, ): m_getuid.return_value = 0 expected_error_tmpl = messages.INVALID_SERVICE_OP_FAILURE m_ent1_cls = mock.Mock() m_ent1_obj = m_ent1_cls.return_value m_ent1_obj.enable.return_value = (False, None) m_ent2_cls = mock.Mock() m_ent2_cls.name = "ent2" m_ent2_is_beta = mock.PropertyMock(return_value=True) type(m_ent2_cls).is_beta = m_ent2_is_beta m_ent2_obj = m_ent2_cls.return_value m_ent2_obj.enable.return_value = ( False, CanEnableFailure(CanEnableFailureReason.IS_BETA), ) m_ent3_cls = mock.Mock() m_ent3_cls.name = "ent3" m_ent3_is_beta = mock.PropertyMock(return_value=False) type(m_ent3_cls).is_beta = m_ent3_is_beta m_ent3_obj = m_ent3_cls.return_value m_ent3_obj.enable.return_value = (True, None) def factory_side_effect(cfg, name, not_found_okay=True): if name == "ent2": return m_ent2_cls if name == "ent3": return m_ent3_cls return None m_entitlement_factory.side_effect = factory_side_effect m_valid_services.return_value = ["ent2", "ent3"] cfg = FakeConfig.for_attached_machine() assume_yes = False args_mock = mock.Mock() args_mock.service = ["ent1", "ent2", "ent3"] args_mock.assume_yes = assume_yes args_mock.beta = False expected_msg = "One moment, checking your subscription first\n" with pytest.raises(exceptions.UserFacingError) as err: fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): action_enable(args_mock, cfg) expected_error = expected_error_tmpl.format( operation="enable", invalid_service="ent1, ent2", service_msg=( "Try " + ", ".join(entitlements.valid_services(allow_beta=False)) + "." ), ) assert expected_error.msg == err.value.msg assert expected_msg == fake_stdout.getvalue() for m_ent_cls in [m_ent2_cls, m_ent3_cls]: assert [ mock.call( cfg, assume_yes=assume_yes, allow_beta=False, called_name=m_ent_cls.name, ) ] == m_ent_cls.call_args_list expected_enable_call = mock.call() for m_ent in [m_ent2_obj, m_ent3_obj]: assert [expected_enable_call] == m_ent.enable.call_args_list assert 0 == m_ent1_obj.call_count event.reset() with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_enable)(args_mock, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", } ], "failed_services": ["ent1", "ent2"], "needs_reboot": False, "processed_services": ["ent3"], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_invalid_service_error_message( self, _request_updated_contract, m_getuid, uid, expected_error_template, is_attached, event, FakeConfig, ): """Check invalid service name results in custom error message.""" m_getuid.return_value = uid if is_attached: cfg = FakeConfig.for_attached_machine() service_msg = "\n".join( textwrap.wrap( ( "Try " + ", ".join( entitlements.valid_services( cfg=cfg, allow_beta=True ) ) + "." ), width=80, break_long_words=False, break_on_hyphens=False, ) ) else: cfg = FakeConfig() service_msg = "See https://ubuntu.com/advantage" args = mock.MagicMock() args.service = ["bogus"] args.command = "enable" with pytest.raises(exceptions.UserFacingError) as err: action_enable(args, cfg) if not uid: expected_error = expected_error_template.format( operation="enable", invalid_service="bogus", service_msg=service_msg, ) else: expected_error = expected_error_template assert expected_error.msg == err.value.msg with pytest.raises(SystemExit): with mock.patch.object( event, "_event_logger_mode", event_logger.EventLoggerMode.JSON ): fake_stdout = io.StringIO() with contextlib.redirect_stdout(fake_stdout): main_error_handler(action_enable)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "failure", "errors": [ { "message": expected_error.msg, "message_code": expected_error.name, "service": None, "type": "system", } ], "failed_services": ["bogus"] if not uid and is_attached else [], "needs_reboot": False, "processed_services": [], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
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())
def test_attach_when_one_service_fails_to_enable( self, _m_update_messages, m_request_url, _m_apply_contract_overrides, m_process_entitlement_delta, _m_getuid, FakeConfig, event, ): args = mock.MagicMock(token="token", attach_config=None) cfg = FakeConfig() m_process_entitlement_delta.side_effect = [ ({"test": 123}, True), UserFacingError("error"), ] m_request_url.return_value = ( { "machineToken": "not-null", "machineTokenInfo": { "machineId": "machine-id", "accountInfo": { "id": "acct-1", "name": "acc-name", "createdAt": "2019-06-14T06:45:50Z", "externalAccountIDs": [ {"IDs": ["id1"], "Origin": "AWS"} ], }, "contractInfo": { "id": "cid", "name": "test_contract", "resourceTokens": [ {"token": "token", "type": "test1"}, {"token": "token", "type": "test2"}, ], "resourceEntitlements": [ {"type": "test1", "aptURL": "apt"}, {"type": "test2", "aptURL": "apt"}, ], }, }, }, None, ) fake_stdout = io.StringIO() with pytest.raises(SystemExit): 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_msg = messages.ATTACH_FAILURE_DEFAULT_SERVICES 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": ["test2"], "needs_reboot": False, "processed_services": ["test1"], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())
def test_entitlements_disabled_appropriately( self, m_update_apt_and_motd_msgs, m_client, m_entitlements, m_getuid, m_prompt, prompt_response, assume_yes, expect_disable, FakeConfig, event, capsys, ): # The three parameters: # prompt_response: the user's response to the prompt # assume_yes: the value of the --assume-yes flag in the args passed # to the action # expect_disable: whether or not the enabled entitlement is expected # to be disabled by the action m_getuid.return_value = 0 cfg = FakeConfig.for_attached_machine() fake_client = FakeContractClient(cfg) m_client.return_value = fake_client m_prompt.return_value = prompt_response m_entitlements.ENTITLEMENT_CLASSES = [ entitlement_cls_mock_factory(False), entitlement_cls_mock_factory(True, name="test"), entitlement_cls_mock_factory(False), ] args = mock.MagicMock(assume_yes=assume_yes) return_code = action_detach(args, cfg=cfg) # Check that can_disable is called correctly for ent_cls in m_entitlements.ENTITLEMENT_CLASSES: assert [mock.call(ignore_dependent_services=True) ] == ent_cls.return_value.can_disable.call_args_list assert [mock.call(cfg=cfg, assume_yes=assume_yes)] == ent_cls.call_args_list # Check that disable is only called when can_disable is true for undisabled_cls in [ m_entitlements.ENTITLEMENT_CLASSES[0], m_entitlements.ENTITLEMENT_CLASSES[2], ]: assert 0 == undisabled_cls.return_value.disable.call_count disabled_cls = m_entitlements.ENTITLEMENT_CLASSES[1] if expect_disable: assert [mock.call() ] == disabled_cls.return_value.disable.call_args_list assert 0 == return_code else: assert 0 == disabled_cls.return_value.disable.call_count assert 1 == return_code assert [mock.call(assume_yes=assume_yes)] == m_prompt.call_args_list if expect_disable: assert [mock.call(cfg) ] == m_update_apt_and_motd_msgs.call_args_list cfg = FakeConfig.for_attached_machine() fake_stdout = io.StringIO() # On json response, we will never prompt the user m_prompt.return_value = True with contextlib.redirect_stdout(fake_stdout): with mock.patch.object(event, "_event_logger_mode", event_logger.EventLoggerMode.JSON): main_error_handler(action_detach)(args, cfg) expected = { "_schema_version": event_logger.JSON_SCHEMA_VERSION, "result": "success", "errors": [], "failed_services": [], "needs_reboot": False, "processed_services": ["test"], "warnings": [], } assert expected == json.loads(fake_stdout.getvalue())