def test_errors_are_raised_appropriately( self, _m_getuid, m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, exception_to_throw, exception_type, exception_message, capsys, FakeConfig, ): """Check that simulated status json/yaml output raises errors.""" m_get_contract_information.side_effect = exception_to_throw cfg = FakeConfig() args = mock.MagicMock(format="json", all=False, simulate_with_token="some_token") with pytest.raises(exception_type) as exc: action_status(args, cfg=cfg) assert exc.type == exception_type assert exception_message in getattr(exc.value, "msg", exc.value.args)
def test_is_contract_changed( self, m_add_notice, _m_getuid, _m_get_contract_information, _m_get_available_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, contract_changed, is_attached, capsys, FakeConfig, ): _m_contract_changed.return_value = contract_changed if is_attached: cfg = FakeConfig().for_attached_machine() else: cfg = FakeConfig() action_status(mock.MagicMock(all=False, simulate_with_token=None), cfg=cfg) if is_attached: if contract_changed: assert [ mock.call("", messages.NOTICE_REFRESH_CONTRACT_WARNING) ] == m_add_notice.call_args_list else: assert [ mock.call("", messages.NOTICE_REFRESH_CONTRACT_WARNING) ] not in m_add_notice.call_args_list else: assert _m_contract_changed.call_count == 0
def test_unicode_dash_replacement_when_unprintable( self, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, encoding, expected_dash, FakeConfig, ): # This test can't use capsys because it doesn't emulate sys.stdout # encoding accurately in older versions of pytest underlying_stdout = io.BytesIO() fake_stdout = io.TextIOWrapper(underlying_stdout, encoding=encoding) with mock.patch("sys.stdout", fake_stdout): action_status( mock.MagicMock(all=True, simulate_with_token=None), cfg=FakeConfig.for_attached_machine(), ) fake_stdout.flush() # Make sure all output is in underlying_stdout out = underlying_stdout.getvalue().decode(encoding) # Colour codes are converted to spaces, so strip them out for # comparison out = out.replace(" " * 17, " " * 8) expected_out = ATTACHED_STATUS.format(dash=expected_dash, notices="") assert expected_out == out
def test_error_on_connectivity_errors(self, m_getuid, m_get_avail_resources, capsys): """Raise UrlError on connectivity issues""" m_get_avail_resources.side_effect = util.UrlError( socket.gaierror(-2, "Name or service not known")) cfg = FakeConfig() with pytest.raises(util.UrlError): action_status(mock.MagicMock(), cfg)
def test_wait_blocks_until_lock_released( self, m_sleep, _m_subp, _m_get_version, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, capsys, FakeConfig, ): """Check that --wait will will block and poll until lock released.""" cfg = FakeConfig() lock_file = cfg.data_path("lock") cfg.write_cache("lock", "123:ua auto-attach") def fake_sleep(seconds): if m_sleep.call_count == 3: os.unlink(lock_file) m_sleep.side_effect = fake_sleep assert 0 == action_status(mock.MagicMock(all=False, simulate_with_token=None), cfg=cfg) assert [mock.call(1)] * 3 == m_sleep.call_args_list assert "...\n" + UNATTACHED_STATUS == capsys.readouterr()[0]
def test_unattached_json(self, m_getuid, m_get_avail_resources, capsys, FakeConfig): """Check that unattached status json output is emitted to console""" cfg = FakeConfig() args = mock.MagicMock(format="json") assert 0 == action_status(args, cfg) expected = { "_doc": ("Content provided in json response is currently " "considered Experimental and may change"), "attached": False, "expires": "n/a", "origin": None, "services": [{ "name": "livepatch", "description": "Canonical Livepatch service", "available": "yes", }], "techSupportLevel": "n/a", } assert expected == json.loads(capsys.readouterr()[0])
def test_unattached(self, m_getuid, m_get_avail_resources, capsys, FakeConfig): """Check that unattached status is emitted to console""" cfg = FakeConfig() assert 0 == action_status(mock.MagicMock(), cfg) assert UNATTACHED_STATUS == capsys.readouterr()[0]
def test_error_on_connectivity_errors( self, _m_getuid, _m_get_contract_information, m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, FakeConfig, ): """Raise UrlError on connectivity issues""" m_get_avail_resources.side_effect = exceptions.UrlError( socket.gaierror(-2, "Name or service not known")) cfg = FakeConfig() with pytest.raises(exceptions.UrlError): action_status(mock.MagicMock(all=False, simulate_with_token=None), cfg=cfg)
def test_attached(self, m_getuid, m_get_avail_resources, capsys): """Check that root and non-root will emit attached status""" cfg = FakeConfig.for_attached_machine() assert 0 == action_status(mock.MagicMock(), cfg) # capsys already converts colorized non-printable chars to space # Strip non-printables from output printable_stdout = capsys.readouterr()[0].replace(" " * 17, " " * 8) # On older versions of pytest, capsys doesn't set sys.stdout.encoding # to something that Python parses as UTF-8 compatible, so we get the # ASCII dash; testing for the "wrong" dash here is OK, because we have # a specific test that the correct one is used in # test_unicode_dash_replacement_when_unprintable expected_dash = "-" if sys.stdout.encoding and "UTF-8" in sys.stdout.encoding.upper(): expected_dash = "\u2014" assert ATTACHED_STATUS.format(dash=expected_dash) == printable_stdout
def test_unattached( self, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, capsys, FakeConfig, ): """Check that unattached status is emitted to console""" cfg = FakeConfig() assert 0 == action_status(mock.MagicMock(all=False, simulate_with_token=None), cfg=cfg) assert UNATTACHED_STATUS == capsys.readouterr()[0]
def test_errors_for_token_dates( self, _m_getuid, m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, format_type, event_logger_mode, token_to_use, warning_message, contract_field, date_value, capsys, FakeConfig, event, ): """Check errors for expired tokens, and not valid yet tokens.""" def contract_info_side_effect(cfg, token): response = copy.deepcopy(RESPONSE_CONTRACT_INFO) response["contractInfo"][contract_field] = date_value return response m_get_contract_information.side_effect = contract_info_side_effect cfg = FakeConfig() args = mock.MagicMock(format=format_type, all=False, simulate_with_token=token_to_use) with mock.patch.object(event, "_event_logger_mode", event_logger_mode), mock.patch.object( event, "_command", "status"): assert 1 == action_status(args, cfg=cfg) if format_type == "json": output = json.loads(capsys.readouterr()[0]) else: output = yaml.safe_load(capsys.readouterr()[0]) assert output["errors"][0]["message"] == warning_message
def test_wait_blocks_until_lock_released( self, m_sleep, m_subp, m_getuid, m_get_avail_resources, capsys, FakeConfig, ): """Check that --wait will will block and poll until lock released.""" cfg = FakeConfig() lock_file = cfg.data_path("lock") cfg.write_cache("lock", "123:ua auto-attach") def fake_sleep(seconds): if m_sleep.call_count == 3: os.unlink(lock_file) m_sleep.side_effect = fake_sleep assert 0 == action_status(mock.MagicMock(), cfg) assert [mock.call(1)] * 3 == m_sleep.call_args_list assert "...\n" + UNATTACHED_STATUS == capsys.readouterr()[0]
def test_attached( self, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, notices, notice_status, use_all, capsys, FakeConfig, ): """Check that root and non-root will emit attached status""" cfg = FakeConfig.for_attached_machine() cfg.write_cache("notices", notices) assert 0 == action_status(mock.MagicMock(all=use_all, simulate_with_token=None), cfg=cfg) # capsys already converts colorized non-printable chars to space # Strip non-printables from output printable_stdout = capsys.readouterr()[0].replace(" " * 17, " " * 8) # On older versions of pytest, capsys doesn't set sys.stdout.encoding # to something that Python parses as UTF-8 compatible, so we get the # ASCII dash; testing for the "wrong" dash here is OK, because we have # a specific test that the correct one is used in # test_unicode_dash_replacement_when_unprintable expected_dash = "-" status_tmpl = ATTACHED_STATUS if use_all else ATTACHED_STATUS_NOBETA if sys.stdout.encoding and "UTF-8" in sys.stdout.encoding.upper(): expected_dash = "\u2014" assert (status_tmpl.format(dash=expected_dash, notices=notice_status) == printable_stdout)
def test_simulated_formats( self, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, use_all, format_type, event_logger_mode, capsys, FakeConfig, event, ): """Check that simulated status json output is emitted to console""" cfg = FakeConfig() args = mock.MagicMock(format=format_type, all=use_all, simulate_with_token="some_token") with mock.patch.object(event, "_event_logger_mode", event_logger_mode), mock.patch.object( event, "_command", "status"): assert 0 == action_status(args, cfg=cfg) expected_services = [ { "auto_enabled": "yes", "available": "yes", "description": "UA Apps: Extended Security Maintenance (ESM)", "entitled": "no", "name": "esm-apps", }, { "auto_enabled": "yes", "available": "no", "description": "UA Infra: Extended Security Maintenance (ESM)", "entitled": "yes", "name": "esm-infra", }, { "auto_enabled": "no", "available": "no", "description": "NIST-certified core packages", "entitled": "no", "name": "fips", }, { "auto_enabled": "no", "available": "no", "description": "NIST-certified core packages with priority" " security updates", "entitled": "no", "name": "fips-updates", }, { "auto_enabled": "no", "available": "yes", "description": "Canonical Livepatch service", "entitled": "yes", "name": "livepatch", }, { "auto_enabled": "no", "available": "no", "description": "Beta-version Ubuntu Kernel with PREEMPT_RT" " patches", "entitled": "no", "name": "realtime-kernel", }, { "auto_enabled": "no", "available": "no", "description": "Security Updates for the Robot Operating" " System", "entitled": "no", "name": "ros", }, { "auto_enabled": "no", "available": "no", "description": "All Updates for the Robot Operating System", "entitled": "no", "name": "ros-updates", }, ] if not use_all: expected_services = expected_services[1:-3] expected = { "_doc": "Content provided in json response is currently considered" " Experimental and may change", "_schema_version": "0.1", "attached": False, "machine_id": None, "notices": [], "account": { "created_at": "2019-06-14T06:45:50Z", "external_account_ids": [], "id": "some_id", "name": "Name", }, "contract": { "created_at": "2021-05-21T20:00:53Z", "id": "some_id", "name": "Name", "products": ["uai-essential-virtual"], "tech_support_level": "essential", }, "environment_vars": [], "execution_status": "inactive", "execution_details": "No Ubuntu Advantage operations are running", "expires": "9999-12-31T00:00:00Z", "effective": None, "services": expected_services, "simulated": True, "version": version.get_version(features=cfg.features), "config_path": None, "config": { "data_dir": mock.ANY }, "errors": [], "warnings": [], "result": "success", } if format_type == "json": assert expected == json.loads(capsys.readouterr()[0]) else: assert expected == yaml.safe_load(capsys.readouterr()[0])
def test_attached_formats( self, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, use_all, environ, format_type, event_logger_mode, capsys, FakeConfig, event, ): """Check that unattached status json output is emitted to console""" cfg = FakeConfig.for_attached_machine() args = mock.MagicMock(format=format_type, all=use_all, simulate_with_token=None) with mock.patch.object(os, "environ", environ): with mock.patch.object(event, "_event_logger_mode", event_logger_mode), mock.patch.object( event, "_command", "status"): assert 0 == action_status(args, cfg=cfg) expected_environment = [] if environ: expected_environment = [ { "name": "UA_CONFIG_FILE", "value": "config_file" }, { "name": "UA_DATA_DIR", "value": "data_dir" }, { "name": "UA_FEATURES_ALLOW_BETA", "value": "true" }, ] if use_all: services = SERVICES_JSON_ALL else: services = [ svc for svc in SERVICES_JSON_ALL if svc["name"] not in BETA_SVC_NAMES ] inapplicable_services = [ service["name"] for service in RESPONSE_AVAILABLE_SERVICES if not service["available"] ] filtered_services = [ service for service in services if service["name"] not in inapplicable_services ] if format_type == "json": contract_created_at = "2020-05-08T19:02:26+00:00" account_created_at = "2019-06-14T06:45:50+00:00" expires = "2040-05-08T19:02:26+00:00" effective = "2000-05-08T19:02:26+00:00" else: contract_created_at = datetime.datetime( 2020, 5, 8, 19, 2, 26, tzinfo=datetime.timezone.utc) account_created_at = datetime.datetime( 2019, 6, 14, 6, 45, 50, tzinfo=datetime.timezone.utc) expires = datetime.datetime(2040, 5, 8, 19, 2, 26, tzinfo=datetime.timezone.utc) effective = datetime.datetime(2000, 5, 8, 19, 2, 26, tzinfo=datetime.timezone.utc) tech_support_level = status.UserFacingStatus.INAPPLICABLE.value expected = { "_doc": ("Content provided in json response is currently " "considered Experimental and may change"), "_schema_version": "0.1", "version": version.get_version(features=cfg.features), "execution_status": status.UserFacingConfigStatus.INACTIVE.value, "execution_details": messages.NO_ACTIVE_OPERATIONS, "attached": True, "machine_id": "test_machine_id", "effective": effective, "expires": expires, "notices": [], "services": filtered_services, "environment_vars": expected_environment, "contract": { "id": "cid", "name": "test_contract", "created_at": contract_created_at, "products": ["free"], "tech_support_level": tech_support_level, }, "account": { "id": "acct-1", "name": "test_account", "created_at": account_created_at, "external_account_ids": [{ "IDs": ["id1"], "Origin": "AWS" }], }, "config_path": None, "config": { "data_dir": mock.ANY }, "simulated": False, "errors": [], "warnings": [], "result": "success", } if format_type == "json": assert expected == json.loads(capsys.readouterr()[0]) else: yaml_output = yaml.safe_load(capsys.readouterr()[0]) # On earlier versions of pyyaml, we don't add the timezone # info when converting a date string into a datetime object. # Since we only want to test if we are producing a valid # yaml file in the status output, we can manually add # the timezone info to make the test work as expected for key, value in yaml_output.items(): if isinstance(value, datetime.datetime): yaml_output[key] = value.replace( tzinfo=datetime.timezone.utc) elif isinstance(value, dict): for inner_key, inner_value in value.items(): if isinstance(inner_value, datetime.datetime): yaml_output[key][inner_key] = inner_value.replace( tzinfo=datetime.timezone.utc) assert expected == yaml_output
def test_unattached_formats( self, _m_getuid, _m_get_contract_information, _m_get_avail_resources, _m_should_reboot, _m_remove_notice, _m_contract_changed, use_all, environ, format_type, event_logger_mode, capsys, FakeConfig, event, ): """Check that unattached status json output is emitted to console""" cfg = FakeConfig() args = mock.MagicMock(format=format_type, all=use_all, simulate_with_token=None) with mock.patch.object(os, "environ", environ): with mock.patch.object(event, "_event_logger_mode", event_logger_mode), mock.patch.object( event, "_command", "status"): assert 0 == action_status(args, cfg=cfg) expected_environment = [] if environ: expected_environment = [ { "name": "UA_CONFIG_FILE", "value": "config_file" }, { "name": "UA_DATA_DIR", "value": "data_dir" }, { "name": "UA_FEATURES_ALLOW_BETA", "value": "true" }, ] expected_services = [ { "name": "esm-apps", "description": "UA Apps: Extended Security Maintenance (ESM)", "available": "yes", }, { "name": "livepatch", "description": "Canonical Livepatch service", "available": "yes", }, ] if not use_all: expected_services.pop(0) expected = { "_doc": ("Content provided in json response is currently " "considered Experimental and may change"), "_schema_version": "0.1", "version": version.get_version(features=cfg.features), "execution_status": status.UserFacingConfigStatus.INACTIVE.value, "execution_details": messages.NO_ACTIVE_OPERATIONS, "attached": False, "machine_id": None, "effective": None, "expires": None, "notices": [], "services": expected_services, "environment_vars": expected_environment, "contract": { "id": "", "name": "", "created_at": "", "products": [], "tech_support_level": "n/a", }, "account": { "name": "", "id": "", "created_at": "", "external_account_ids": [], }, "config_path": None, "config": { "data_dir": mock.ANY }, "simulated": False, "errors": [], "warnings": [], "result": "success", } if format_type == "json": assert expected == json.loads(capsys.readouterr()[0]) else: assert expected == yaml.safe_load(capsys.readouterr()[0])