예제 #1
0
    def test_can_enable_when_incompatible_service_found(
        self, concrete_entitlement_factory
    ):
        base_ent = concrete_entitlement_factory(
            entitled=True,
            applicability_status=(ApplicabilityStatus.APPLICABLE, ""),
            application_status=(ApplicationStatus.DISABLED, ""),
        )

        m_entitlement_cls = mock.MagicMock()
        m_entitlement_obj = m_entitlement_cls.return_value
        m_entitlement_obj.application_status.return_value = [
            ApplicationStatus.ENABLED,
            "",
        ]
        base_ent._incompatible_services = (
            base.IncompatibleService(
                m_entitlement_cls, messages.NamedMessage("test", "test")
            ),
        )

        ret, reason = base_ent.can_enable()

        assert ret is False
        assert reason.reason == CanEnableFailureReason.INCOMPATIBLE_SERVICE
        assert reason.message is None
    def test_proc_file_is_used_to_determine_application_status_message(
            self, proc_content, path_exists, entitlement):
        orig_load_file = util.load_file

        def fake_load_file(path):
            if path == "/proc/sys/crypto/fips_enabled":
                return proc_content
            return orig_load_file(path)

        orig_exists = os.path.exists

        def fake_exists(path):
            if path == "/proc/sys/crypto/fips_enabled":
                return path_exists
            return orig_exists(path)

        msg = messages.NamedMessage("test-code", "sure is some status here")
        entitlement.cfg.add_notice("",
                                   messages.FIPS_SYSTEM_REBOOT_REQUIRED.msg)

        if path_exists:
            entitlement.cfg.add_notice("", messages.FIPS_REBOOT_REQUIRED_MSG)

        if proc_content == "0":
            entitlement.cfg.add_notice("",
                                       messages.FIPS_DISABLE_REBOOT_REQUIRED)

        with mock.patch(
                M_PATH + "repo.RepoEntitlement.application_status",
                return_value=(ApplicationStatus.ENABLED, msg),
        ):
            with mock.patch("uaclient.util.load_file") as m_load_file:
                m_load_file.side_effect = fake_load_file
                with mock.patch("os.path.exists") as m_path_exists:
                    m_path_exists.side_effect = fake_exists
                    (
                        actual_status,
                        actual_msg,
                    ) = entitlement.application_status()

        expected_status = ApplicationStatus.ENABLED
        if path_exists and proc_content == "1":
            expected_msg = msg
            assert entitlement.cfg.read_cache("notices") is None
        elif path_exists and proc_content == "0":
            expected_msg = messages.FIPS_PROC_FILE_ERROR.format(
                file_name=entitlement.FIPS_PROC_FILE)
            expected_status = ApplicationStatus.DISABLED
            assert [["", messages.NOTICE_FIPS_MANUAL_DISABLE_URL]
                    ] == entitlement.cfg.read_cache("notices")
        else:
            expected_msg = messages.FIPS_REBOOT_REQUIRED
            assert [["", messages.FIPS_SYSTEM_REBOOT_REQUIRED.msg]
                    ] == entitlement.cfg.read_cache("notices")

        assert actual_status == expected_status
        assert expected_msg.msg == actual_msg.msg
        assert expected_msg.name == actual_msg.name
예제 #3
0
    def test_enable_false_when_fails_to_enable_required_service(
        self,
        m_handle_msg,
        m_ent_factory,
        m_prompt_for_confirmation,
        enable_fail_message,
        concrete_entitlement_factory,
    ):
        m_handle_msg.return_value = True

        fail_reason = CanEnableFailure(
            CanEnableFailureReason.INACTIVE_REQUIRED_SERVICES
        )

        if enable_fail_message:
            msg = messages.NamedMessage("test-code", enable_fail_message)
        else:
            msg = None

        enable_fail_reason = CanEnableFailure(
            CanEnableFailureReason.NOT_ENTITLED, message=msg
        )

        m_ent_cls = mock.Mock()
        m_ent_obj = m_ent_cls.return_value
        m_ent_obj.enable.return_value = (False, enable_fail_reason)
        m_ent_obj.application_status.return_value = (
            ApplicationStatus.DISABLED,
            None,
        )
        type(m_ent_obj).title = mock.PropertyMock(return_value="Test")
        m_ent_factory.return_value = m_ent_cls

        m_prompt_for_confirmation.return_vale = True

        entitlement = concrete_entitlement_factory(
            entitled=True,
            application_status=(ApplicationStatus.DISABLED, ""),
        )
        entitlement._required_services = "test"

        with mock.patch.object(entitlement, "can_enable") as m_can_enable:
            m_can_enable.return_value = (False, fail_reason)
            ret, fail = entitlement.enable()

        assert not ret
        expected_msg = "Cannot enable required service: Test"
        if enable_fail_reason.message:
            expected_msg += "\n" + enable_fail_reason.message.msg
        assert expected_msg == fail.message.msg
        assert 1 == m_can_enable.call_count
        assert 1 == m_ent_factory.call_count
예제 #4
0
    def test_disable_false_when_fails_to_disable_dependent_service(
        self,
        m_handle_msg,
        m_ent_factory,
        m_prompt_for_confirmation,
        disable_fail_message,
        concrete_entitlement_factory,
    ):
        m_handle_msg.return_value = True

        fail_reason = CanDisableFailure(
            CanDisableFailureReason.ACTIVE_DEPENDENT_SERVICES
        )
        if disable_fail_message:
            msg = messages.NamedMessage("test-code", disable_fail_message)
        else:
            msg = None

        disable_fail_reason = CanDisableFailure(
            CanDisableFailureReason.ALREADY_DISABLED, message=msg
        )

        m_ent_cls = mock.Mock()
        m_ent_obj = m_ent_cls.return_value
        m_ent_obj.disable.return_value = (False, disable_fail_reason)
        m_ent_obj.application_status.return_value = (
            ApplicationStatus.ENABLED,
            None,
        )
        type(m_ent_obj).title = mock.PropertyMock(return_value="Test")
        m_ent_factory.return_value = m_ent_cls

        m_prompt_for_confirmation.return_vale = True

        entitlement = concrete_entitlement_factory(
            entitled=True,
            application_status=(ApplicationStatus.DISABLED, ""),
            dependent_services=("test"),
        )

        with mock.patch.object(entitlement, "can_disable") as m_can_disable:
            m_can_disable.return_value = (False, fail_reason)
            ret, fail = entitlement.disable()

        assert not ret
        expected_msg = "Cannot disable dependent service: Test"
        if disable_fail_reason.message:
            expected_msg += "\n" + disable_fail_reason.message.msg
        assert expected_msg == fail.message.msg
        assert 1 == m_can_disable.call_count
        assert 1 == m_ent_factory.call_count
예제 #5
0
    def test_enable_when_incompatible_service_found(
        self,
        m_prompt,
        m_is_config_value_true,
        block_disable_on_enable,
        assume_yes,
        concrete_entitlement_factory,
    ):
        m_prompt.return_value = assume_yes
        m_is_config_value_true.return_value = block_disable_on_enable
        base_ent = concrete_entitlement_factory(
            entitled=True,
            enable=True,
            applicability_status=(ApplicabilityStatus.APPLICABLE, ""),
            application_status=(ApplicationStatus.DISABLED, ""),
        )

        m_entitlement_cls = mock.MagicMock()
        m_entitlement_obj = m_entitlement_cls.return_value
        m_entitlement_obj.application_status.return_value = [
            ApplicationStatus.ENABLED,
            "",
        ]
        base_ent._incompatible_services = (
            base.IncompatibleService(
                m_entitlement_cls, messages.NamedMessage("test", "test")
            ),
        )

        ret, reason = base_ent.enable()

        expected_prompt_call = 1
        if block_disable_on_enable:
            expected_prompt_call = 0

        expected_ret = False
        expected_reason = CanEnableFailureReason.INCOMPATIBLE_SERVICE
        if assume_yes and not block_disable_on_enable:
            expected_ret = True
            expected_reason = None
        expected_disable_call = 1 if expected_ret else 0

        assert ret == expected_ret
        if expected_reason is None:
            assert reason is None
        else:
            assert reason.reason == expected_reason
        assert m_prompt.call_count == expected_prompt_call
        assert m_is_config_value_true.call_count == 1
        assert m_entitlement_obj.disable.call_count == expected_disable_call
    def application_status(
        self,
    ) -> Tuple[ApplicationStatus, Optional[messages.NamedMessage]]:
        status = (ApplicationStatus.ENABLED, None)

        if not util.which(LIVEPATCH_CMD):
            return (ApplicationStatus.DISABLED, messages.LIVEPATCH_NOT_ENABLED)

        try:
            util.subp(
                [LIVEPATCH_CMD, "status"], retry_sleeps=LIVEPATCH_RETRIES
            )
        except exceptions.ProcessExecutionError as e:
            # TODO(May want to parse INACTIVE/failure assessment)
            logging.debug("Livepatch not enabled. %s", str(e))
            return (
                ApplicationStatus.DISABLED,
                messages.NamedMessage(name="", msg=str(e)),
            )
        return status
예제 #7
0
    def test_status(
        self,
        contract_status,
        uf_status,
        in_inapplicable_resources,
        expected_status,
        FakeConfig,
    ):
        ent = mock.MagicMock()
        ent.name = "test_entitlement"
        ent.contract_status.return_value = contract_status
        ent.user_facing_status.return_value = (
            uf_status,
            messages.NamedMessage("test-code", ""),
        )

        unavailable_resources = ({
            ent.name: ""
        } if in_inapplicable_resources else {})
        ret = status._attached_service_status(ent, unavailable_resources)

        assert expected_status == ret["status"]
    def test_entitlement_instantiated_and_disabled(
        self,
        m_status,
        m_valid_services,
        m_entitlement_factory,
        _m_getuid,
        disable_return,
        return_code,
        assume_yes,
        service,
        tmpdir,
        capsys,
        event,
        FakeConfig,
    ):
        entitlements_cls = []
        entitlements_obj = []
        ent_dict = {}
        m_valid_services.return_value = []

        if not disable_return:
            fail = CanDisableFailure(
                CanDisableFailureReason.ALREADY_DISABLED,
                message=messages.NamedMessage("test-code", "test"),
            )
        else:
            fail = None

        for entitlement_name in service:
            m_entitlement_cls = mock.Mock()

            m_entitlement = m_entitlement_cls.return_value
            m_entitlement.disable.return_value = (disable_return, fail)

            entitlements_obj.append(m_entitlement)
            entitlements_cls.append(m_entitlement_cls)
            m_valid_services.return_value.append(entitlement_name)
            ent_dict[entitlement_name] = m_entitlement_cls
            type(m_entitlement).name = mock.PropertyMock(
                return_value=entitlement_name)

        def factory_side_effect(cfg, name, ent_dict=ent_dict):
            return ent_dict.get(name)

        m_entitlement_factory.side_effect = factory_side_effect

        cfg = FakeConfig.for_attached_machine()
        args_mock = mock.Mock()
        args_mock.service = service
        args_mock.assume_yes = assume_yes

        with mock.patch.object(cfg, "check_lock_info", return_value=(-1, "")):
            ret = action_disable(args_mock, cfg=cfg)

        for m_entitlement_cls in entitlements_cls:
            assert [mock.call(cfg, assume_yes=assume_yes)
                    ] == m_entitlement_cls.call_args_list

        expected_disable_call = mock.call()
        for m_entitlement in entitlements_obj:
            assert [expected_disable_call
                    ] == m_entitlement.disable.call_args_list

        assert return_code == ret
        assert len(entitlements_cls) == m_status.call_count

        cfg = FakeConfig.for_attached_machine()
        args_mock.assume_yes = True
        args_mock.format = "json"
        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):
                        ret = action_disable(args_mock, cfg=cfg)

        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "success" if disable_return else "failure",
            "errors": [],
            "failed_services": [] if disable_return else service,
            "needs_reboot": False,
            "processed_services": service if disable_return else [],
            "warnings": [],
        }

        if not disable_return:
            expected["errors"] = [{
                "message": "test",
                "message_code": "test-code",
                "service": ent_name,
                "type": "service",
            } for ent_name in service]

        assert return_code == ret
        assert expected == json.loads(fake_stdout.getvalue())
    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())
예제 #10
0
    def test_print_message_when_can_enable_fails(
        self,
        _m_get_available_resources,
        _m_request_updated_contract,
        m_getuid,
        event,
        FakeConfig,
    ):
        m_getuid.return_value = 0
        m_entitlement_cls = mock.Mock()
        type(m_entitlement_cls).is_beta = mock.PropertyMock(return_value=False)
        m_entitlement_obj = m_entitlement_cls.return_value
        m_entitlement_obj.enable.return_value = (
            False,
            CanEnableFailure(
                CanEnableFailureReason.ALREADY_ENABLED,
                message=messages.NamedMessage("test-code", "msg"),
            ),
        )

        cfg = FakeConfig.for_attached_machine()
        args_mock = mock.Mock()
        args_mock.service = ["ent1"]
        args_mock.assume_yes = False
        args_mock.beta = False

        with mock.patch(
            "uaclient.entitlements.entitlement_factory",
            return_value=m_entitlement_cls,
        ), mock.patch(
            "uaclient.entitlements.valid_services", return_value=["ent1"]
        ):
            fake_stdout = io.StringIO()
            with contextlib.redirect_stdout(fake_stdout):
                action_enable(args_mock, cfg)

            assert (
                "One moment, checking your subscription first\nmsg\n"
                == fake_stdout.getvalue()
            )

        with mock.patch(
            "uaclient.entitlements.entitlement_factory",
            return_value=m_entitlement_cls,
        ), mock.patch(
            "uaclient.entitlements.valid_services", return_value=["ent1"]
        ), mock.patch.object(
            event, "_event_logger_mode", event_logger.EventLoggerMode.JSON
        ):
            fake_stdout = io.StringIO()
            with contextlib.redirect_stdout(fake_stdout):
                ret = action_enable(args_mock, cfg=cfg)

        expected_ret = 1
        expected = {
            "_schema_version": event_logger.JSON_SCHEMA_VERSION,
            "result": "failure",
            "errors": [
                {
                    "message": "msg",
                    "message_code": "test-code",
                    "service": "ent1",
                    "type": "service",
                }
            ],
            "failed_services": ["ent1"],
            "needs_reboot": False,
            "processed_services": [],
            "warnings": [],
        }
        assert expected == json.loads(fake_stdout.getvalue())
        assert expected_ret == ret
예제 #11
0
class TestAttachedServiceStatus:
    @pytest.mark.parametrize(
        "contract_status,uf_status,in_inapplicable_resources,expected_status",
        ATTACHED_SERVICE_STATUS_PARAMETERS,
    )
    def test_status(
        self,
        contract_status,
        uf_status,
        in_inapplicable_resources,
        expected_status,
        FakeConfig,
    ):
        ent = mock.MagicMock()
        ent.name = "test_entitlement"
        ent.contract_status.return_value = contract_status
        ent.user_facing_status.return_value = (
            uf_status,
            messages.NamedMessage("test-code", ""),
        )

        unavailable_resources = ({
            ent.name: ""
        } if in_inapplicable_resources else {})
        ret = status._attached_service_status(ent, unavailable_resources)

        assert expected_status == ret["status"]

    @pytest.mark.parametrize(
        "blocking_incompatible_services, expected_blocked_by",
        (
            ([], []),
            (
                [
                    IncompatibleService(FIPSEntitlement,
                                        messages.NamedMessage("code", "msg"))
                ],
                [{
                    "name": "fips",
                    "reason": "msg",
                    "reason_code": "code"
                }],
            ),
            (
                [
                    IncompatibleService(FIPSEntitlement,
                                        messages.NamedMessage("code", "msg")),
                    IncompatibleService(ROSEntitlement,
                                        messages.NamedMessage("code2",
                                                              "msg2")),
                ],
                [
                    {
                        "name": "fips",
                        "reason": "msg",
                        "reason_code": "code"
                    },
                    {
                        "name": "ros",
                        "reason": "msg2",
                        "reason_code": "code2"
                    },
                ],
            ),
        ),
    )
    def test_blocked_by(
        self,
        blocking_incompatible_services,
        expected_blocked_by,
        tmpdir,
        FakeConfig,
    ):
        ent = ConcreteTestEntitlement(
            blocking_incompatible_services=blocking_incompatible_services)
        service_status = status._attached_service_status(ent, [])
        assert service_status["blocked_by"] == expected_blocked_by
예제 #12
0
    def test_attached_reports_contract_and_service_status(
        self,
        m_repo_contract_status,
        m_repo_uf_status,
        m_esm_contract_status,
        m_esm_uf_status,
        m_livepatch_contract_status,
        m_livepatch_uf_status,
        _m_livepatch_status,
        _m_fips_status,
        _m_getuid,
        _m_should_reboot,
        m_remove_notice,
        entitlements,
        features_override,
        show_beta,
        FakeConfig,
    ):
        """When attached, return contract and service user-facing status."""
        m_repo_contract_status.return_value = ContractStatus.ENTITLED
        m_repo_uf_status.return_value = (
            UserFacingStatus.INAPPLICABLE,
            messages.NamedMessage("test-code", "repo details"),
        )
        m_livepatch_contract_status.return_value = ContractStatus.ENTITLED
        m_livepatch_uf_status.return_value = (
            UserFacingStatus.ACTIVE,
            messages.NamedMessage("test-code", "livepatch details"),
        )
        m_esm_contract_status.return_value = ContractStatus.ENTITLED
        m_esm_uf_status.return_value = (
            UserFacingStatus.ACTIVE,
            messages.NamedMessage("test-code", "esm-apps details"),
        )
        token = {
            "availableResources": ALL_RESOURCES_AVAILABLE,
            "machineTokenInfo": {
                "machineId": "test_machine_id",
                "accountInfo": {
                    "id": "1",
                    "name": "accountname",
                    "createdAt": "2019-06-14T06:45:50Z",
                    "externalAccountIDs": [{
                        "IDs": ["id1"],
                        "Origin": "AWS"
                    }],
                },
                "contractInfo": {
                    "id": "contract-1",
                    "name": "contractname",
                    "createdAt": "2020-05-08T19:02:26Z",
                    "resourceEntitlements": entitlements,
                    "products": ["free"],
                },
            },
        }
        cfg = FakeConfig.for_attached_machine(account_name="accountname",
                                              machine_token=token)
        if features_override:
            cfg.override_features(features_override)
        if not entitlements:
            support_level = UserFacingStatus.INAPPLICABLE.value
        else:
            support_level = entitlements[0]["affordances"]["supportLevel"]
        expected = copy.deepcopy(status.DEFAULT_STATUS)
        expected.update({
            "version": version.get_version(features=cfg.features),
            "attached": True,
            "machine_id": "test_machine_id",
            "contract": {
                "name":
                "contractname",
                "id":
                "contract-1",
                "created_at":
                datetime.datetime(2020,
                                  5,
                                  8,
                                  19,
                                  2,
                                  26,
                                  tzinfo=datetime.timezone.utc),
                "products": ["free"],
                "tech_support_level":
                support_level,
            },
            "account": {
                "name":
                "accountname",
                "id":
                "1",
                "created_at":
                datetime.datetime(2019,
                                  6,
                                  14,
                                  6,
                                  45,
                                  50,
                                  tzinfo=datetime.timezone.utc),
                "external_account_ids": [{
                    "IDs": ["id1"],
                    "Origin": "AWS"
                }],
            },
        })
        for cls in ENTITLEMENT_CLASSES:
            if cls.name == "livepatch":
                expected_status = UserFacingStatus.ACTIVE.value
                details = "livepatch details"
            elif cls.name == "esm-apps":
                expected_status = UserFacingStatus.ACTIVE.value
                details = "esm-apps details"
            else:
                expected_status = UserFacingStatus.INAPPLICABLE.value
                details = "repo details"

            if self.check_beta(cls, show_beta, cfg, expected_status):
                continue

            expected["services"].append({
                "name": cls.name,
                "description": cls.description,
                "entitled": ContractStatus.ENTITLED.value,
                "status": expected_status,
                "status_details": details,
                "description_override": None,
                "available": mock.ANY,
                "blocked_by": [],
            })
        with mock.patch(
                "uaclient.status._get_config_status") as m_get_cfg_status:
            m_get_cfg_status.return_value = DEFAULT_CFG_STATUS
            assert expected == status.status(cfg=cfg, show_beta=show_beta)

        assert len(ENTITLEMENT_CLASSES) - 2 == m_repo_uf_status.call_count
        assert 1 == m_livepatch_uf_status.call_count

        expected_calls = [
            mock.call(
                "",
                messages.NOTICE_DAEMON_AUTO_ATTACH_LOCK_HELD.format(
                    operation=".*"),
            ),
            mock.call("", messages.NOTICE_DAEMON_AUTO_ATTACH_FAILED),
            mock.call(
                "",
                messages.ENABLE_REBOOT_REQUIRED_TMPL.format(
                    operation="fix operation"),
            ),
        ]

        assert expected_calls == m_remove_notice.call_args_list
    def test_help_command_when_attached(self, m_attached,
                                        m_available_resources, ent_status,
                                        ent_msg, is_beta):
        """Test help command for a valid service in an attached ua client."""
        m_args = mock.MagicMock()
        m_service_name = mock.PropertyMock(return_value="test")
        type(m_args).service = m_service_name
        m_all = mock.PropertyMock(return_value=True)
        type(m_args).all = m_all

        m_entitlement_cls = mock.MagicMock()
        m_ent_help_info = mock.PropertyMock(
            return_value="Test service\nService is being tested")
        m_is_beta = mock.PropertyMock(return_value=is_beta)
        type(m_entitlement_cls).is_beta = m_is_beta
        m_entitlement_obj = m_entitlement_cls.return_value
        type(m_entitlement_obj).help_info = m_ent_help_info

        m_entitlement_obj.contract_status.return_value = ent_status
        m_entitlement_obj.user_facing_status.return_value = (
            status.UserFacingStatus.ACTIVE,
            messages.NamedMessage("test-code", "active"),
        )
        m_ent_name = mock.PropertyMock(return_value="test")
        type(m_entitlement_obj).name = m_ent_name
        m_ent_desc = mock.PropertyMock(return_value="description")
        type(m_entitlement_obj).description = m_ent_desc

        m_attached.return_value = True
        m_available_resources.return_value = [{
            "name": "test",
            "available": True
        }]

        status_msg = "enabled" if ent_msg == "yes" else "—"
        ufs_call_count = 1 if ent_msg == "yes" else 0
        ent_name_call_count = 2 if ent_msg == "yes" else 1
        is_beta_call_count = 1 if status_msg == "enabled" else 0

        expected_msgs = [
            "Name:\ntest",
            "Entitled:\n{}".format(ent_msg),
            "Status:\n{}".format(status_msg),
        ]

        if is_beta and status_msg == "enabled":
            expected_msgs.append("Beta:\nTrue")

        expected_msgs.append(
            "Help:\nTest service\nService is being tested\n\n")

        expected_msg = "\n\n".join(expected_msgs)

        fake_stdout = io.StringIO()
        with mock.patch(
                "uaclient.status.entitlement_factory",
                return_value=m_entitlement_cls,
        ):
            with contextlib.redirect_stdout(fake_stdout):
                action_help(m_args, cfg=None)

        assert expected_msg.strip() == fake_stdout.getvalue().strip()
        assert 1 == m_service_name.call_count
        assert 1 == m_ent_help_info.call_count
        assert 1 == m_available_resources.call_count
        assert 1 == m_attached.call_count
        assert 1 == m_ent_desc.call_count
        assert is_beta_call_count == m_is_beta.call_count
        assert ent_name_call_count == m_ent_name.call_count
        assert 1 == m_entitlement_obj.contract_status.call_count
        assert (
            ufs_call_count == m_entitlement_obj.user_facing_status.call_count)