# -*- coding: utf-8 -*-
"""unittests for message.py"""

# pylint: disable=C0302, C0415, R0904, R0913, R0914, R0915, W0212
import unittest
import sys
import importlib
import configparser
from unittest.mock import patch, MagicMock

sys.path.insert(0, ".")
sys.path.insert(1, "..")


class FakeDBStore(object):
    """face DBStore class needed for mocking"""

    # pylint: disable=W0107, R0903
    pass


class TestACMEHandler(unittest.TestCase):
    """test class for ACMEHandler"""

    acme = None

    @patch.dict("os.environ", {"ACME_SRV_CONFIGFILE": "ACME_SRV_CONFIGFILE"})
    def setUp(self):
        """setup unittest"""
        models_mock = MagicMock()
        modules = {"acme2certifier.acme_srv.db_handler": models_mock}
        patch.dict("sys.modules", modules).start()
        import logging

        logging.basicConfig(level=logging.CRITICAL)
        self.logger = logging.getLogger("test_a2c")
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)

    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_001_message_check_decoding_error(self, mock_decode):
        """message_check failed bcs of decoding error"""
        message = '{"foo" : "bar"}'
        mock_decode.return_value = (False, "detail", None, None, None)
        self.assertEqual(
            (400, "urn:ietf:params:acme:error:malformed", "detail", None, None, None),
            self.message.check(message),
        )

    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_002_message_check_nonce_failed(self, mock_decode, mock_nonce_check):
        """message_check nonce check failed"""
        message = '{"foo" : "bar"}'
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (400, "badnonce", None)
        self.assertEqual(
            (400, "badnonce", None, "protected", "payload", None),
            self.message.check(message),
        )

    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_003_message_check_account_lookup_failed(
        self, mock_decode, mock_nonce_check
    ):
        """message check failed bcs account id lookup failed"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (200, None, None)
        self.message.eabkid_check_disable = True
        message = '{"foo" : "bar"}'
        self.assertEqual(
            (
                403,
                "urn:ietf:params:acme:error:accountDoesNotExist",
                None,
                "protected",
                "payload",
                None,
            ),
            self.message.check(message),
        )

    @patch("acme2certifier.acme_srv.message.Message._check_and_handle_invalid_eab_credentials")
    @patch("acme2certifier.acme_srv.signature.Signature.check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_004_message_check_signature_failed(
        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk
    ):
        """message check failed bcs signature_check_failed"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (200, None, None)
        mock_aname.return_value = "account_name"
        mock_eabchk.return_value = "account_name"
        mock_sig.return_value = (False, "error", "detail")
        message = '{"foo" : "bar"}'
        self.assertEqual(
            (403, "error", "detail", "protected", "payload", "account_name"),
            self.message.check(message),
        )

    @patch("acme2certifier.acme_srv.message.Message._check_and_handle_invalid_eab_credentials")
    @patch("acme2certifier.acme_srv.signature.Signature.check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_005_message_check_invalid_eab_credentials(
        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk
    ):
        """message check failed bcs signature_check_failed"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (200, None, None)
        mock_aname.return_value = "account_name"
        mock_eabchk.return_value = None
        mock_sig.return_value = (False, "error", "detail")
        message = '{"foo" : "bar"}'
        self.message.config.eabkid_check_disable = False
        self.assertEqual(
            (
                403,
                "urn:ietf:params:acme:error:unauthorized",
                "invalid eab credentials",
                "protected",
                "payload",
                None,
            ),
            self.message.check(message),
        )

    @patch("acme2certifier.acme_srv.message.Message._check_and_handle_invalid_eab_credentials")
    @patch("acme2certifier.acme_srv.signature.Signature.check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_006_message_check_successful(
        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk
    ):
        """message check successful"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (200, None, None)
        mock_aname.return_value = "account_name"
        mock_eabchk.return_value = "account_name"
        mock_sig.return_value = (True, None, None)
        message = '{"foo" : "bar"}'
        self.assertEqual(
            (200, None, None, "protected", "payload", "account_name"),
            self.message.check(message),
        )

    @patch("acme2certifier.acme_srv.message.Message._check_and_handle_invalid_eab_credentials")
    @patch("acme2certifier.acme_srv.signature.Signature.check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_007_message_check_nonce_disabled(
        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk
    ):
        """message check successful as nonce check is disabled"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (400, "badnonce", None)
        mock_aname.return_value = "account_name"
        mock_sig.return_value = (True, None, None)
        mock_eabchk.return_value = "account_name"
        message = '{"foo" : "bar"}'
        self.message.config.nonce_check_disable = True
        self.message.config.signature_check_disable = True
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertEqual(
                (200, None, None, "protected", "payload", "account_name"),
                self.message.check(message, skip_nonce_check=True),
            )
        self.assertIn(
            "ERROR:test_a2c:**** NONCE CHECK DISABLED!!! Severe security issue ****",
            lcm.output,
        )

    @patch("acme2certifier.acme_srv.message.Message._check_and_handle_invalid_eab_credentials")
    @patch("acme2certifier.acme_srv.signature.Signature.check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_008_message_check_signature_nonce_disabled(
        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eabchk
    ):
        """message check successful as nonce check is disabled"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (400, "badnonce", None)
        mock_aname.return_value = "account_name"
        mock_sig.return_value = (True, None, None)
        self.message.config.eabkid_check_disable = True
        self.message.config.nonce_check_disable = True
        self.message.config.signature_check_disable = True
        message = '{"foo" : "bar"}'

        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertEqual(
                (200, None, None, "protected", "payload", "account_name"),
                self.message.check(message, skip_nonce_check=True),
            )
        self.assertIn(
            "ERROR:test_a2c:**** SIGNATURE_CHECK_DISABLE!!! Severe security issue ****",
            lcm.output,
        )
        self.assertIn(
            "ERROR:test_a2c:**** NONCE CHECK DISABLED!!! Severe security issue ****",
            lcm.output,
        )
        self.assertFalse(mock_eabchk.called)

    @patch("acme2certifier.acme_srv.message.Message._check_and_handle_invalid_eab_credentials")
    @patch("acme2certifier.acme_srv.signature.Signature.check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.nonce.Nonce.check")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_009_message_check_nonce_disabled_keyrollover(
        self, mock_decode, mock_nonce_check, mock_aname, mock_sig, mock_eab_chk
    ):
        """message check successful as nonce check is disabled"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_nonce_check.return_value = (400, "badnonce", None)
        mock_aname.return_value = "account_name"
        mock_sig.return_value = (True, None, None)
        mock_eab_chk.return_value = "account_name"
        message = '{"foo" : "bar"}'
        self.message.config.nonce_check_disable = False
        self.message.config.signature_check_disable = True
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertEqual(
                (200, None, None, "protected", "payload", "account_name"),
                self.message.check(message, skip_nonce_check=True),
            )
        self.assertIn(
            "INFO:test_a2c:Skip nonce check of inner payload during keyrollover",
            lcm.output,
        )

    @patch("acme2certifier.acme_srv.nonce.Nonce.generate_and_add")
    def test_010_message_prepare_response_complete_data(self, mock_nnonce):
        """Message.prepare_respons for code 200 and complete data"""
        data_dic = {
            "data": {"foo_data": "bar_bar"},
            "header": {"foo_header": "bar_header"},
        }
        mock_nnonce.return_value = "new_nonce"
        config_dic = {"code": 200, "message": "message", "detail": "detail"}
        self.assertEqual(
            {
                "header": {"foo_header": "bar_header", "Replay-Nonce": "new_nonce"},
                "code": 200,
                "data": {"foo_data": "bar_bar"},
            },
            self.message.prepare_response(data_dic, config_dic),
        )

    @patch("acme2certifier.acme_srv.error.Error.enrich_error")
    @patch("acme2certifier.acme_srv.nonce.Nonce.generate_and_add")
    def test_011_message_prepare_response_no_header(self, mock_nnonce, mock_error):
        """Message.prepare_respons for code 200 without header tag in response_dic"""
        data_dic = {
            "data": {"foo_data": "bar_bar"},
        }
        mock_nnonce.return_value = "new_nonce"
        mock_error.return_value = "mock_error"
        config_dic = {"code": 200, "message": "message", "detail": "detail"}
        self.assertEqual(
            {
                "header": {"Replay-Nonce": "new_nonce"},
                "code": 200,
                "data": {"foo_data": "bar_bar"},
            },
            self.message.prepare_response(data_dic, config_dic),
        )

    @patch("acme2certifier.acme_srv.nonce.Nonce.generate_and_add")
    def test_012_message_prepare_response_no_code(self, mock_nnonce):
        """Message.prepare_response for config_dic without code key"""
        data_dic = {
            "data": {"foo_data": "bar_bar"},
            "header": {"foo_header": "bar_header"},
        }
        mock_nnonce.return_value = "new_nonce"
        # mock_error.return_value = 'mock_error'
        config_dic = {"message": "type", "detail": "detail"}
        self.assertEqual(
            {
                "header": {"Replay-Nonce": "new_nonce", "foo_header": "bar_header"},
                "code": 500,
                "data": {
                    "detail": "http status code missing",
                    "type": "urn:ietf:params:acme:error:serverInternal",
                    "status": 500,
                },
            },
            self.message.prepare_response(data_dic, config_dic),
        )

    @patch("acme2certifier.acme_srv.nonce.Nonce.generate_and_add")
    def test_013_message_prepare_response_no_message(self, mock_nnonce):
        """Message.prepare_response for config_dic without message key"""
        data_dic = {
            "data": {"foo_data": "bar_bar"},
            "header": {"foo_header": "bar_header"},
        }
        mock_nnonce.return_value = "new_nonce"
        # mock_error.return_value = 'mock_error'
        config_dic = {"code": 400, "detail": "detail"}
        self.assertEqual(
            {
                "header": {"Replay-Nonce": "new_nonce", "foo_header": "bar_header"},
                "code": 400,
                "data": {
                    "detail": "detail",
                    "type": "urn:ietf:params:acme:error:serverInternal",
                    "status": 400,
                },
            },
            self.message.prepare_response(data_dic, config_dic),
        )

    @patch("acme2certifier.acme_srv.nonce.Nonce.generate_and_add")
    def test_014_message_prepare_response_no_detail(self, mock_nnonce):
        """Message.repare_response for config_dic without detail key"""
        data_dic = {
            "data": {"foo_data": "bar_bar"},
            "header": {"foo_header": "bar_header"},
        }
        mock_nnonce.return_value = "new_nonce"
        config_dic = {"code": 400, "type": "message"}
        self.assertEqual(
            {
                "header": {"Replay-Nonce": "new_nonce", "foo_header": "bar_header"},
                "code": 400,
                "data": {"type": "message", "status": 400},
            },
            self.message.prepare_response(data_dic, config_dic),
        )

    @patch("acme2certifier.acme_srv.error.Error.enrich_error")
    @patch("acme2certifier.acme_srv.nonce.Nonce.generate_and_add")
    def test_015_message_prepare_response_no_data(self, mock_nnonce, mock_error):
        """Message.prepare_response for response_dic without data key"""
        data_dic = {"header": {"foo_header": "bar_header"}}
        mock_nnonce.return_value = "new_nonce"
        mock_error.return_value = "mock_error"
        config_dic = {"code": 400, "type": "message", "detail": "detail"}
        self.assertEqual(
            {
                "header": {"Replay-Nonce": "new_nonce", "foo_header": "bar_header"},
                "code": 400,
                "data": {"detail": "mock_error", "type": "message", "status": 400},
            },
            self.message.prepare_response(data_dic, config_dic),
        )

    def test_016_message_name_get_empty_content(self):
        """test Message.name_get() with empty content"""
        protected = {}
        self.assertFalse(self.message._extract_account_name_from_content(protected))

    def test_017_message_name_get_kid_nonsense(self):
        """test Message.name_get() with kid with nonsens in content"""
        protected = {"kid": "foo"}
        self.assertEqual(
            "foo", self.message._extract_account_name_from_content(protected)
        )

    def test_018_message_name_get_wrong_kid(self):
        """test Message.name_get() with wrong kid in content"""
        protected = {"kid": "http://tester.local/acme/account/account_name"}
        self.assertEqual(
            None, self.message._extract_account_name_from_content(protected)
        )

    def test_019_message_name_get_correct_kid(self):
        """test Message.name_get() with correct kid in content"""
        protected = {"kid": "http://tester.local/acme/acct/account_name"}
        self.assertEqual(
            "account_name", self.message._extract_account_name_from_content(protected)
        )

    def test_020_message_name_get_jwk_no_url(self):
        """test Message.name_get() with 'jwk' in content but without URL"""
        protected = {"jwk": "jwk"}
        self.assertEqual(
            None, self.message._extract_account_name_from_content(protected)
        )

    def test_021_message_name_get_jwk_wrong_url(self):
        """test Message.name_get() with 'jwk' and 'url' in content but url is wrong"""
        protected = {"jwk": "jwk", "url": "url"}
        self.assertEqual(
            None, self.message._extract_account_name_from_content(protected)
        )

    def test_022_message__name_get(self):
        """test Message.name_get() with 'jwk' and correct 'url' in content but no 'n' in jwk"""
        protected = {"jwk": "jwk", "url": "http://tester.local/acme/revokecert"}
        self.assertEqual(
            None, self.message._extract_account_name_from_content(protected)
        )

    def test_023_message__name_get(self):
        """test Message.name_get() with 'jwk' and correct 'url' but account lookup failed"""
        protected = {"jwk": {"n": "n"}, "url": "http://tester.local/acme/revokecert"}
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.return_value = {}
        self.assertEqual(
            None, self.message._extract_account_name_from_content(protected)
        )

    def test_024_message__name_get(self):
        """test Message.name_get() with 'jwk' and correct 'url' and wrong account lookup data"""
        protected = {"jwk": {"n": "n"}, "url": "http://tester.local/acme/revokecert"}
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.return_value = {"bar": "foo"}
        self.assertEqual(
            None, self.message._extract_account_name_from_content(protected)
        )

    def test_025_message__name_get(self):
        """test Message.name_get() with 'jwk' and correct 'url' and wrong account lookup data"""
        protected = {"jwk": {"n": "n"}, "url": "http://tester.local/acme/revokecert"}
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.return_value = {"name": "foo"}
        self.assertEqual(
            "foo", self.message._extract_account_name_from_content(protected)
        )

    def test_026_message__name_get(self):
        """test Message.name_get() - dbstore.account_lookup raises an exception"""
        protected = {"jwk": {"n": "n"}, "url": "http://tester.local/acme/revokecert"}
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = Exception("exc_mess__name_get")
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.message._extract_account_name_from_content(protected)
        self.assertIn(
            "CRITICAL:test_a2c:Database error: failed to look up account name for revocation: exc_mess__name_get",
            lcm.output,
        )

    def test_027__enter__(self):
        """test enter"""
        self.message.__enter__()

    @patch("acme2certifier.acme_srv.message.load_config")
    def test_028_config_load(self, mock_load_cfg):
        """test _config_load empty config"""
        parser = configparser.ConfigParser()
        # parser['Account'] = {'foo': 'bar'}
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.load_config")
    def test_029_config_load(self, mock_load_cfg):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["Nonce"] = {
            "nonce_check_disable": False,
            "signature_check_disable": False,
        }
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.load_config")
    def test_030_config_load(self, mock_load_cfg):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["Nonce"] = {
            "nonce_check_disable": True,
            "signature_check_disable": False,
        }
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertTrue(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.load_config")
    def test_031_config_load(self, mock_load_cfg):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["Nonce"] = {
            "nonce_check_disable": False,
            "signature_check_disable": True,
        }
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertTrue(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.load_config")
    def test_032_config_load(self, mock_load_cfg):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["Directory"] = {"url_prefix": "url_prefix", "foo": "bar"}
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertEqual("url_prefix/acme/acct/", self.message.config.acct_path)
        self.assertEqual(
            "url_prefix/acme/revokecert", self.message.config.revocation_path
        )
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_033_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load explicit false in cfg"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {
            "eab_handler_file": "eab_handler_file",
            "eabkid_check_disable": False,
        }
        mock_load_cfg.return_value = parser
        mock_eab.return_value = MagicMock()
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        # self.message._config_load()
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertFalse(self.message.config.eabkid_check_disable)
        self.assertTrue(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_034_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {
            "eab_handler_file": "eab_handler_file",
            "eabkid_check_disable": True,
        }
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_035_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {"foo": "bar", "eabkid_check_disable": True}
        mock_load_cfg.return_value = parser
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_036_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load wrong eab handler config"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {"foo": "bar", "invalid_eabkid_deactivate": True}
        mock_load_cfg.return_value = parser
        mock_eab.return_value = None
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.message._load_configuration()
        self.assertIn(
            "CRITICAL:test_a2c:EABHandler configuration incomplete",
            lcm.output,
        )
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_136_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load empty config"""
        parser = configparser.ConfigParser()
        parser["CAHandler"] = {"foo": "bar"}
        mock_load_cfg.return_value = parser
        mock_eab.return_value = None
        self.message._load_configuration()
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_037_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {
            "eab_handler_file": "eab_handler_file",
            "eabkid_check_disable": True,
        }
        mock_load_cfg.return_value = parser
        mock_eab.return_value = None
        # with self.assertLogs('test_a2c', level='INFO') as lcm:
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        # self.assertIn('CRITICAL:test_a2c:Account._config_load(): EABHandler could not get loaded', lcm.output)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_038_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load eab_load returned None"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {"eab_handler_file": "eab_handler_file"}
        mock_load_cfg.return_value = parser
        mock_eab.return_value = None
        from acme2certifier.acme_srv.message import Message

        with self.assertLogs("test_a2c", level="INFO") as lcm:

            self.message = Message(False, "http://tester.local", self.logger)
        self.assertIn(
            "CRITICAL:test_a2c:EABHandler could not get loaded",
            lcm.output,
        )
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertFalse(self.message.config.eabkid_check_disable)
        self.assertTrue(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_039_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {
            "eab_handler_file": "eab_handler_file",
            "invalid_eabkid_deactivate": True,
        }
        mock_load_cfg.return_value = parser
        mock_eab.return_value = MagicMock()
        # with self.assertLogs('test_a2c', level='INFO') as lcm:
        from acme2certifier.acme_srv.message import Message

        self.message = Message(False, "http://tester.local", self.logger)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertFalse(self.message.config.eabkid_check_disable)
        self.assertTrue(mock_eab.called)
        self.assertTrue(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.eab_handler_load")
    @patch("acme2certifier.acme_srv.message.load_config")
    def test_040_config_load(self, mock_load_cfg, mock_eab):
        """test _config_load"""
        parser = configparser.ConfigParser()
        parser["EABhandler"] = {
            "eab_handler_file": "eab_handler_file",
            "invalid_eabkid_deactivate": True,
            "eabkid_check_disable": True,
        }
        mock_load_cfg.return_value = parser
        mock_eab.return_value = None
        # with self.assertLogs('test_a2c', level='INFO') as lcm:
        self.message._load_configuration()
        # self.assertIn('CRITICAL:test_a2c:Account._config_load(): EABHandler could not get loaded', lcm.output)
        self.assertFalse(self.message.config.nonce_check_disable)
        self.assertFalse(self.message.config.signature_check_disable)
        self.assertTrue(self.message.config.eabkid_check_disable)
        self.assertFalse(mock_eab.called)
        self.assertFalse(self.message.config.invalid_eabkid_deactivate)

    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_041_message_check(self, mock_decode):
        """cli_check failed bcs of decoding error"""
        message = '{"foo" : "bar"}'
        mock_decode.return_value = (False, "detail", None, None, None)
        self.assertEqual(
            (
                400,
                "urn:ietf:params:acme:error:malformed",
                "detail",
                None,
                None,
                None,
                {},
            ),
            self.message.cli_check(message),
        )

    @patch("acme2certifier.acme_srv.signature.Signature.cli_check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_042_message_check(self, mock_decode, mock_name_get, mock_check):
        """message check failed bcs sig.cli_check() failed"""
        self.message.dbstore = MagicMock()
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_check.return_value = (False, "error", "detail")
        mock_name_get.return_value = "name"
        message = '{"foo" : "bar"}'
        self.assertEqual(
            (403, "error", "detail", "protected", "payload", "name", {}),
            self.message.cli_check(message),
        )
        self.assertFalse(self.message.dbstore.cli_permissions_get.called)

    @patch("acme2certifier.acme_srv.signature.Signature.cli_check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_043_message_check(self, mock_decode, mock_name_get, mock_check):
        """message check failed bcs sig.cli_check() successful"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_check.return_value = ("True", "error", "detail")
        self.message.repo = MagicMock()
        self.message.repo.cli_permissions_get.return_value = {"foo": "bar"}
        mock_name_get.return_value = "name"
        message = '{"foo" : "bar"}'
        self.assertEqual(
            (200, None, None, "protected", "payload", "name", {"foo": "bar"}),
            self.message.cli_check(message),
        )

    @patch("acme2certifier.acme_srv.signature.Signature.cli_check")
    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    @patch("acme2certifier.acme_srv.message.decode_message")
    def test_044_message_check(self, mock_decode, mock_name_get, mock_check):
        """message check failed bcs sig.cli_check() successful"""
        mock_decode.return_value = (True, None, "protected", "payload", "signature")
        mock_check.return_value = ("True", "error", "detail")
        self.message.repo = MagicMock()
        self.message.repo.cli_permissions_get.side_effect = Exception("db error")
        mock_name_get.return_value = "name"
        message = '{"foo" : "bar"}'
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertEqual(
                (200, None, None, "protected", "payload", "name", {}),
                self.message.cli_check(message),
            )
        self.assertIn(
            "ERROR:test_a2c:cli_permissions_get failed: db error",
            lcm.output,
        )

    def test_044_invalid_eab_check(self):
        """test _invalid_eab_check - ok"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = None
        self.message.repo.account_lookup.return_value = {"eab_kid": "eab_kid"}
        eab_handler_module = importlib.import_module(
            "acme2certifier.eabhandlers.skeleton_eab_handler"
        )
        self.message.config.eab_handler = eab_handler_module.EABhandler
        self.message.config.eab_handler.mac_key_get = MagicMock(return_value="mac_key")
        self.assertEqual(
            "account_name",
            self.message._check_and_handle_invalid_eab_credentials("account_name"),
        )

    def test_045_invalid_eab_check(self):
        """test _invalid_eab_check - ok"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = None
        self.message.repo.account_lookup.return_value = {"eab_kid": "eab_kid"}
        eab_handler_module = importlib.import_module(
            "acme2certifier.eabhandlers.skeleton_eab_handler"
        )
        self.message.config.eab_handler = eab_handler_module.EABhandler
        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertFalse(
                self.message._check_and_handle_invalid_eab_credentials("account_name")
            )
        self.assertIn(
            "ERROR:test_a2c:EAB credentials: eab_kid could not be found in eab-credential store.",
            lcm.output,
        )

    def test_046_invalid_eab_check(self):
        """test _invalid_eab_check - ok"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = None
        self.message.repo.account_update.side_effect = None
        self.message.repo.account_lookup.return_value = {"eab_kid": "eab_kid"}
        eab_handler_module = importlib.import_module(
            "acme2certifier.eabhandlers.skeleton_eab_handler"
        )
        self.message.config.eab_handler = eab_handler_module.EABhandler
        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)
        self.message.config.invalid_eabkid_deactivate = True
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertFalse(
                self.message._check_and_handle_invalid_eab_credentials("account_name")
            )
        self.assertIn(
            "ERROR:test_a2c:EAB credentials: eab_kid could not be found in eab-credential store.",
            lcm.output,
        )
        self.assertIn(
            "ERROR:test_a2c:Account account_name will be deactivated due to missing eab credentials",
            lcm.output,
        )
        self.assertTrue(self.message.repo.account_update.called)

    def test_047_invalid_eab_check(self):
        """test _invalid_eab_check - ok"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = None
        self.message.repo.account_lookup.return_value = {"foo": "bar"}
        eab_handler_module = importlib.import_module(
            "acme2certifier.eabhandlers.skeleton_eab_handler"
        )
        self.message.config.eab_handler = eab_handler_module.EABhandler
        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertFalse(
                self.message._check_and_handle_invalid_eab_credentials("account_name")
            )
        self.assertIn(
            "ERROR:test_a2c:Account account_name has no eab credentials", lcm.output
        )

    def test_048_invalid_eab_check(self):
        """test _invalid_eab_check - ok"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = None
        self.message.repo.account_lookup.return_value = None
        eab_handler_module = importlib.import_module(
            "acme2certifier.eabhandlers.skeleton_eab_handler"
        )
        self.message.config.eab_handler = eab_handler_module.EABhandler
        self.message.config.eab_handler.mac_key_get = MagicMock(return_value=None)
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertFalse(
                self.message._check_and_handle_invalid_eab_credentials("account_name")
            )
        self.assertIn(
            "ERROR:test_a2c:Account lookup for account_name failed.", lcm.output
        )

    def test_048b_invalid_eab_check_non_strict_no_eab_kid(self):
        """test _invalid_eab_check allows missing eab_kid in non-strict mode"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = None
        self.message.repo.account_lookup.return_value = {"foo": "bar"}
        self.message.config.eab_strict_mode = False
        self.message.config.eab_handler = None

        with self.assertLogs("test_a2c", level="DEBUG") as lcm:
            self.assertEqual(
                "account_name",
                self.message._check_and_handle_invalid_eab_credentials("account_name"),
            )
        self.assertIn(
            "DEBUG:test_a2c:Message._check_and_handle_invalid_eab_credentials() eab credentials validated or not required for account_name: account_name",
            lcm.output,
        )

    def test_049__safe_account_lookup_db_error(self):
        """test _safe_account_lookup - dbstore.account_lookup raises an exception"""
        self.message.repo = MagicMock()
        self.message.repo.account_lookup.side_effect = Exception(
            "exc_safe_account_lookup"
        )
        with self.assertLogs("test_a2c", level="INFO") as lcm:
            self.assertEqual(None, self.message._safe_account_lookup("account_name"))
        self.assertIn(
            "ERROR:test_a2c:Account lookup for account_name failed: exc_safe_account_lookup",
            lcm.output,
        )

    def test_050_eab_mac_key_exists_exception(self):
        """Test _eab_mac_key_exists handles exception and returns False"""
        self.message.config.eab_handler = MagicMock()
        # Simulate eab_handler raising an exception when called
        self.message.config.eab_handler.side_effect = Exception("handler error")
        with self.assertLogs("test_a2c", level="ERROR") as lcm:
            result = self.message._eab_mac_key_exists("dummy_kid")
        self.assertFalse(result)
        self.assertIn("ERROR:test_a2c:EAB handler error: handler error", lcm.output)

    def test_051_handle_missing_eab_credentials_db_error(self):
        """Test _handle_missing_eab_credentials handles db error gracefully"""
        self.message.repo = MagicMock()
        self.message.repo.account_update.side_effect = Exception("db error")
        self.message.config.invalid_eabkid_deactivate = True
        with self.assertLogs("test_a2c", level="ERROR") as lcm:
            self.message._handle_missing_eab_credentials("dummy_account", "eab_kid")
        self.assertIn(
            "ERROR:test_a2c:EAB credentials: eab_kid could not be found in eab-credential store.",
            lcm.output,
        )
        self.assertIn(
            "ERROR:test_a2c:Account dummy_account will be deactivated due to missing eab credentials",
            lcm.output,
        )
        self.assertIn(
            "ERROR:test_a2c:Account update failed: db error",
            lcm.output,
        )

    @patch("acme2certifier.acme_srv.message.Message._extract_account_name_from_content")
    def test_052_extract_account_name_from_content(self, mock_name_get):
        """Test _extract_account_name_from_content handles unexpected exceptions gracefully"""
        mock_name_get.return_value = "account_name"
        protected = {"jwk": {"n": "n"}, "url": "http://tester.local/acme/revokecert"}
        self.assertEqual(
            "account_name", self.message.extract_account_name_from_content(protected)
        )


class TestAccountRepository(unittest.TestCase):
    """Unit tests for AccountRepository class"""

    def setUp(self):
        self.mock_dbstore = MagicMock()
        self.repo = importlib.import_module("acme2certifier.acme_srv.message").AccountRepository(
            self.mock_dbstore
        )

    def test_account_lookup_calls_dbstore(self):
        self.mock_dbstore.account_lookup.return_value = "result"
        result = self.repo.account_lookup("key", "value")
        self.mock_dbstore.account_lookup.assert_called_once_with("key", "value")
        self.assertEqual(result, "result")

    def test_account_update_calls_dbstore(self):
        self.mock_dbstore.account_update.return_value = "updated"
        result = self.repo.account_update({"foo": "bar"}, True)
        self.mock_dbstore.account_update.assert_called_once_with({"foo": "bar"}, True)
        self.assertEqual(result, "updated")

    def test_cli_permissions_get_calls_dbstore(self):
        self.mock_dbstore.cli_permissions_get.return_value = {"perm": True}
        result = self.repo.cli_permissions_get("account_name")
        self.mock_dbstore.cli_permissions_get.assert_called_once_with("account_name")
        self.assertEqual(result, {"perm": True})


if __name__ == "__main__":
    unittest.main()
