# -*- coding: utf-8 -*-
"""Comprehensive unit tests for authorization module"""

import sys
from unittest.mock import MagicMock

# Patch sys.modules to mock DBstore and db_handler import everywhere
sys.modules["acme2certifier.acme_srv.db_handler"] = MagicMock()
sys.modules["acme2certifier.acme_srv.authorization.DBstore"] = MagicMock()

import sys
import os
import unittest
from configparser import ConfigParser
from unittest.mock import Mock, MagicMock, patch, call
import json
import types

# Add the parent directory to sys.path so we can import acme_srv
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

# Import classes to test
from acme2certifier.acme_srv.authorization import (
    Authorization,
    AuthorizationRepository,
    AuthorizationBusinessLogic,
    ChallengeSetManager,
    AuthorizationConfiguration,
    AuthorizationData,
    AuthorizationError,
    AuthorizationNotFoundError,
    AuthorizationExpiredError,
    ConfigurationError,
)


class TestAuthorizationConfiguration(unittest.TestCase):
    """Test AuthorizationConfiguration dataclass"""

    def test_001_config_default_values(self):
        """Test default configuration values"""
        config = AuthorizationConfiguration()
        self.assertEqual(config.validity, 86400)
        self.assertFalse(config.expiry_check_disable)
        self.assertEqual(config.authz_path, "/acme/authz/")

    def test_002_config_custom_values(self):
        """Test custom configuration values"""
        config = AuthorizationConfiguration(
            validity=172800, expiry_check_disable=True, authz_path="/custom/authz/"
        )
        self.assertEqual(config.validity, 172800)
        self.assertTrue(config.expiry_check_disable)
        self.assertEqual(config.authz_path, "/custom/authz/")


class TestAuthorizationData(unittest.TestCase):
    """Test AuthorizationData dataclass"""

    def test_003_data_creation_required_fields(self):
        """Test AuthorizationData creation with required fields only"""
        data = AuthorizationData(
            name="test_authz", status="pending", expires=1234567890, token="test_token"
        )
        self.assertEqual(data.name, "test_authz")
        self.assertEqual(data.status, "pending")
        self.assertEqual(data.expires, 1234567890)
        self.assertEqual(data.token, "test_token")
        self.assertIsNone(data.identifier)
        self.assertIsNone(data.challenges)
        self.assertFalse(data.wildcard)

    def test_004_data_creation_all_fields(self):
        """Test AuthorizationData creation with all fields"""
        identifier = {"type": "dns", "value": "example.com"}
        challenges = [{"type": "http-01", "token": "test_token"}]

        data = AuthorizationData(
            name="test_authz",
            status="valid",
            expires=1234567890,
            token="test_token",
            identifier=identifier,
            challenges=challenges,
            wildcard=True,
        )
        self.assertEqual(data.identifier, identifier)
        self.assertEqual(data.challenges, challenges)
        self.assertTrue(data.wildcard)

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_005_data_to_dict_basic(self, mock_uts_to_date):
        """Test to_dict method with basic fields"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        data = AuthorizationData(
            name="test_authz", status="pending", expires=1234567890, token="test_token"
        )

        result = data.to_dict()
        expected = {"status": "pending", "expires": "2021-01-01T00:00:00Z"}
        self.assertEqual(result, expected)
        mock_uts_to_date.assert_called_once_with(1234567890)

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_006_data_to_dict_with_identifier(self, mock_uts_to_date):
        """Test to_dict method with identifier"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"
        identifier = {"type": "dns", "value": "example.com"}

        data = AuthorizationData(
            name="test_authz",
            status="valid",
            expires=1234567890,
            token="test_token",
            identifier=identifier,
        )

        result = data.to_dict()
        expected = {
            "status": "valid",
            "expires": "2021-01-01T00:00:00Z",
            "identifier": identifier,
        }
        self.assertEqual(result, expected)

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_007_data_to_dict_with_wildcard(self, mock_uts_to_date):
        """Test to_dict method with wildcard flag"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        data = AuthorizationData(
            name="test_authz",
            status="valid",
            expires=1234567890,
            token="test_token",
            wildcard=True,
        )

        result = data.to_dict()
        expected = {
            "status": "valid",
            "expires": "2021-01-01T00:00:00Z",
            "wildcard": True,
        }
        self.assertEqual(result, expected)

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_008_data_to_dict_with_challenges(self, mock_uts_to_date):
        """Test to_dict method with challenges"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"
        challenges = [{"type": "http-01", "token": "test_token"}]

        data = AuthorizationData(
            name="test_authz",
            status="valid",
            expires=1234567890,
            token="test_token",
            challenges=challenges,
        )

        result = data.to_dict()
        expected = {
            "status": "valid",
            "expires": "2021-01-01T00:00:00Z",
            "challenges": challenges,
        }
        self.assertEqual(result, expected)


class TestAuthorizationRepository(unittest.TestCase):
    """Test AuthorizationRepository class"""

    def setUp(self):
        self.mock_dbstore = Mock()
        self.mock_logger = Mock()
        self.repository = AuthorizationRepository(self.mock_dbstore, self.mock_logger)

    def test_009_repository_initialization(self):
        """Test repository initialization"""
        self.assertEqual(self.repository.dbstore, self.mock_dbstore)
        self.assertEqual(self.repository.logger, self.mock_logger)

    def test_010_find_authorization_by_name_success(self):
        """Test successful authorization lookup by name"""
        expected_result = {"name": "test_authz", "status": "valid"}
        self.mock_dbstore.authorization_lookup.return_value = [expected_result]

        result = self.repository.find_authorization_by_name("test_authz")
        self.assertEqual(result, expected_result)
        self.mock_dbstore.authorization_lookup.assert_called_once_with(
            "name", "test_authz"
        )
        self.mock_logger.debug.assert_called_with(
            "AuthorizationRepository.find_authorization_by_name(%s)", "test_authz"
        )

    def test_011_find_authorization_by_name_with_field_list(self):
        """Test authorization lookup with field list"""
        expected_result = {"name": "test_authz", "status": "valid"}
        field_list = ["name", "status", "expires"]
        self.mock_dbstore.authorization_lookup.return_value = [expected_result]

        result = self.repository.find_authorization_by_name("test_authz", field_list)
        self.assertEqual(result, expected_result)
        self.mock_dbstore.authorization_lookup.assert_called_once_with(
            "name", "test_authz", field_list
        )

    def test_012_find_authorization_by_name_not_found(self):
        """Test authorization lookup when not found"""
        self.mock_dbstore.authorization_lookup.return_value = []

        result = self.repository.find_authorization_by_name("nonexistent")

        self.assertIsNone(result)

    def test_013_find_authorization_by_name_empty_result(self):
        self.mock_dbstore.authorization_lookup.return_value = None
        result = self.repository.find_authorization_by_name("test_authz")
        self.assertIsNone(result)

    def test_014_find_authorization_by_name_database_error(self):
        """Test authorization lookup with database error"""
        self.mock_dbstore.authorization_lookup.side_effect = Exception(
            "Database connection failed"
        )

        with self.assertRaises(AuthorizationError) as context:
            self.repository.find_authorization_by_name("test_authz")

        self.assertIn(
            "Failed to find authorization 'test_authz': Database connection failed",
            str(context.exception),
        )
        self.mock_logger.critical.assert_called_once()

    def test_015_update_authorization_expiry_success(self):
        """Test successful authorization expiry update"""
        self.repository.update_authorization_expiry(
            "test_authz", "new_token", 1234567890
        )

        expected_update = {
            "name": "test_authz",
            "token": "new_token",
            "expires": 1234567890,
        }
        self.mock_dbstore.authorization_update.assert_called_once_with(expected_update)
        self.mock_logger.debug.assert_called_with(
            "AuthorizationRepository.update_authorization_expiry(%s)", "test_authz"
        )

    def test_016_update_authorization_expiry_database_error(self):
        """Test authorization expiry update with database error"""
        self.mock_dbstore.authorization_update.side_effect = Exception("Update failed")

        with self.assertRaises(AuthorizationError) as context:
            self.repository.update_authorization_expiry(
                "test_authz", "new_token", 1234567890
            )

            self.assertIn(
                "Failed to update authorization 'test_authz': Update failed",
                str(context.exception),
            )
            self.mock_logger.error.assert_called()
            log_args = self.mock_logger.error.call_args[0]
            self.assertIn("Database error during authorization update", log_args[0])

    def test_017_search_expired_authorizations_success(self):
        """Test successful expired authorization search"""
        expected_result = [{"name": "expired_authz", "expires": 1000000000}]
        field_list = ["name", "expires"]
        timestamp = 1234567890
        self.mock_dbstore.authorizations_expired_search.return_value = expected_result

        result = self.repository.search_expired_authorizations(timestamp, field_list)

        self.assertEqual(result, expected_result)
        self.mock_dbstore.authorizations_expired_search.assert_called_once_with(
            "expires", timestamp, vlist=field_list, operant="<="
        )

    def test_018_search_expired_authorizations_database_error(self):
        """Test expired authorization search with database error"""
        self.mock_dbstore.authorizations_expired_search.side_effect = Exception(
            "Search failed"
        )

        with self.assertRaises(AuthorizationError) as context:
            self.repository.search_expired_authorizations(1234567890, ["name"])

        self.assertIn(
            "Failed to search expired authorizations: Search failed",
            str(context.exception),
        )
        self.mock_logger.critical.assert_called_once()

    def test_019_mark_authorization_as_expired_success(self):
        """Test successful authorization expiration marking"""
        self.repository.mark_authorization_as_expired("test_authz")

        expected_update = {"name": "test_authz", "status": "expired"}
        self.mock_dbstore.authorization_update.assert_called_once_with(expected_update)
        self.mock_logger.debug.assert_called_with(
            "AuthorizationRepository.mark_authorization_as_expired(%s)", "test_authz"
        )

    def test_020_mark_authorization_as_expired_database_error(self):
        """Test authorization expiration marking with database error"""
        self.mock_dbstore.authorization_update.side_effect = Exception("Expire failed")

        with self.assertRaises(AuthorizationError) as context:
            self.repository.mark_authorization_as_expired("test_authz")

            self.assertIn(
                "Failed to expire authorization 'test_authz': Expire failed",
                str(context.exception),
            )
            self.mock_logger.critical.assert_called()
            log_args = self.mock_logger.critical.call_args[0]
            self.assertIn("Database error: failed to expire authorization", log_args[0])

    def test_021_mark_authorization_as_valid_success(self):
        """Test successful marking of authorization as valid"""
        self.repository.mark_authorization_as_valid("test_authz")
        expected_update = {"name": "test_authz", "status": "valid"}
        self.mock_dbstore.authorization_update.assert_called_once_with(expected_update)
        self.mock_logger.debug.assert_called_with(
            "AuthorizationRepository.mark_authorization_as_valid(%s)", "test_authz"
        )

    def test_022_mark_authorization_as_valid_database_error(self):
        """Test marking authorization as valid with database error"""
        self.mock_dbstore.authorization_update.side_effect = Exception(
            "Mark valid failed"
        )
        with self.assertRaises(AuthorizationError) as context:
            self.repository.mark_authorization_as_valid("test_authz")
            self.assertIn(
                "Failed to mark authorization 'test_authz' as valid: Mark valid failed",
                str(context.exception),
            )
            self.mock_logger.critical.assert_called()
            log_args = self.mock_logger.critical.call_args[0]
            self.assertIn("Database error: failed to mark authorization", log_args[0])

    def test_023_mark_order_as_ready_success(self):
        """Test successful marking of order as ready"""
        self.repository.mark_order_as_ready("test_order")
        expected_update = {"name": "test_order", "status": "ready"}
        self.mock_dbstore.order_update.assert_called_once_with(expected_update)
        self.mock_logger.debug.assert_called_with(
            "AuthorizationRepository.mark_order_as_ready(%s)", "test_order"
        )

    def test_024_mark_order_as_ready_database_error(self):
        """Test marking order as ready with database error"""
        self.mock_dbstore.order_update.side_effect = Exception("Order ready failed")
        with self.assertRaises(AuthorizationError) as context:
            self.repository.mark_order_as_ready("test_order")
            self.assertIn(
                "Failed to mark order 'test_order' as valid: Order ready failed",
                str(context.exception),
            )
            self.mock_logger.critical.assert_called()
            log_args = self.mock_logger.critical.call_args[0]
            self.assertIn("Database error: failed to mark order", log_args[0])


class TestAuthorizationBusinessLogic(unittest.TestCase):
    """Test AuthorizationBusinessLogic class"""

    def setUp(self):
        self.config = AuthorizationConfiguration()
        self.mock_repository = Mock()
        self.mock_logger = Mock()
        self.business_logic = AuthorizationBusinessLogic(
            self.config, self.mock_repository, self.mock_logger
        )

    def test_025_business_logic_initialization(self):
        """Test business logic initialization"""
        self.assertEqual(self.business_logic.config, self.config)
        self.assertEqual(self.business_logic.repository, self.mock_repository)
        self.assertEqual(self.business_logic.logger, self.mock_logger)

    @patch("acme2certifier.acme_srv.authorization.string_sanitize")
    def test_026_extract_authorization_name_from_url(self, mock_sanitize):
        """Test authorization name extraction from URL"""
        mock_sanitize.return_value = "test_authz"
        url = "https://example.com/acme/authz/test_authz"
        server_name = "https://example.com"

        result = self.business_logic.extract_authorization_name_from_url(
            url, server_name
        )

        self.assertEqual(result, "test_authz")
        mock_sanitize.assert_called_once_with(self.mock_logger, "test_authz")

    @patch("acme2certifier.acme_srv.authorization.string_sanitize")
    def test_027_extract_authorization_name_from_url_custom_path(self, mock_sanitize):
        """Test authorization name extraction with custom authz path"""
        mock_sanitize.return_value = "test_authz_custom"
        self.config.authz_path = "/custom/authz/"
        url = "https://example.com/custom/authz/test_authz_custom"
        server_name = "https://example.com"

        result = self.business_logic.extract_authorization_name_from_url(
            url, server_name
        )

        self.assertEqual(result, "test_authz_custom")
        mock_sanitize.assert_called_once_with(self.mock_logger, "test_authz_custom")

    @patch("acme2certifier.acme_srv.authorization.generate_random_string")
    @patch("acme2certifier.acme_srv.authorization.uts_now")
    def test_028_generate_authorization_token_and_expiry(
        self, mock_uts_now, mock_generate_string
    ):
        """Test token and expiry generation"""
        mock_uts_now.return_value = 1000000000
        mock_generate_string.return_value = "random_token"
        self.config.validity = 3600

        token, expires = self.business_logic.generate_authorization_token_and_expiry()

        self.assertEqual(token, "random_token")
        self.assertEqual(expires, 1000003600)  # 1000000000 + 3600
        mock_generate_string.assert_called_once_with(self.mock_logger, 32)

    @patch("acme2certifier.acme_srv.authorization.generate_random_string")
    @patch("acme2certifier.acme_srv.authorization.uts_now")
    def test_028b_resolve_authorization_token_and_expiry_reuse(
        self, mock_uts_now, mock_generate_string
    ):
        """Test resolve reuses existing authorization token and expiry"""
        auth_record = {"token": "existing_token", "expires": 1234567890}

        token, expires, persist = (
            self.business_logic.resolve_authorization_token_and_expiry(auth_record)
        )

        self.assertEqual(token, "existing_token")
        self.assertEqual(expires, 1234567890)
        self.assertFalse(persist)
        mock_generate_string.assert_not_called()
        mock_uts_now.assert_not_called()

    @patch("acme2certifier.acme_srv.authorization.generate_random_string")
    @patch("acme2certifier.acme_srv.authorization.uts_now")
    def test_028c_resolve_authorization_token_and_expiry_generate(
        self, mock_uts_now, mock_generate_string
    ):
        """Test resolve generates token when authorization has none yet"""
        mock_uts_now.return_value = 1000000000
        mock_generate_string.return_value = "random_token"
        self.config.validity = 3600

        token, expires, persist = (
            self.business_logic.resolve_authorization_token_and_expiry(
                {"name": "authz1"}
            )
        )

        self.assertEqual(token, "random_token")
        self.assertEqual(expires, 1000003600)
        self.assertTrue(persist)
        mock_generate_string.assert_called_once_with(self.mock_logger, 32)

    def test_029_enrich_authorization_with_identifier_info_empty(self):
        """Test enrichment with empty auth info"""
        (
            result,
            is_tnauth,
        ) = self.business_logic.enrich_authorization_with_identifier_info(None)

        self.assertEqual(result, {})
        self.assertFalse(is_tnauth)

    def test_030_enrich_authorization_with_identifier_info_dict(self):
        """Test enrichment with auth info as dict"""
        auth_info = {"status__name": "valid", "type": "dns", "value": "example.com"}

        (
            result,
            is_tnauth,
        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)

        expected = {
            "status": "valid",
            "identifier": {"type": "dns", "value": "example.com"},
        }
        self.assertEqual(result, expected)
        self.assertFalse(is_tnauth)

    def test_031_enrich_authorization_with_identifier_info_list(self):
        """Test enrichment with auth info as list"""
        auth_info = [
            {"status__name": "pending", "type": "dns", "value": "test.example.com"}
        ]

        (
            result,
            is_tnauth,
        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)

        expected = {
            "status": "pending",
            "identifier": {"type": "dns", "value": "test.example.com"},
        }
        self.assertEqual(result, expected)
        self.assertFalse(is_tnauth)

    def test_032_enrich_authorization_with_identifier_info_tnauthlist(self):
        """Test enrichment with TNAuthList type"""
        auth_info = {
            "status__name": "valid",
            "type": "TNAuthList",
            "value": "sip:user@example.com",
        }

        (
            result,
            is_tnauth,
        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)

        expected = {
            "status": "valid",
            "identifier": {"type": "TNAuthList", "value": "sip:user@example.com"},
        }
        self.assertEqual(result, expected)
        self.assertTrue(is_tnauth)

    def test_033_enrich_authorization_with_identifier_info_wildcard(self):
        """Test enrichment with wildcard domain"""
        auth_info = {"status__name": "valid", "type": "dns", "value": "*.example.com"}

        (
            result,
            is_tnauth,
        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)

        expected = {
            "status": "valid",
            "identifier": {
                "type": "dns",
                "value": "example.com",  # wildcard prefix removed
            },
            "wildcard": True,
        }
        self.assertEqual(result, expected)
        self.assertFalse(is_tnauth)

    def test_034_enrich_authorization_with_identifier_info_no_type_value(self):
        """Test enrichment with missing type/value"""
        auth_info = {"status__name": "valid"}

        (
            result,
            is_tnauth,
        ) = self.business_logic.enrich_authorization_with_identifier_info(auth_info)

        expected = {"status": "valid"}
        self.assertEqual(result, expected)
        self.assertFalse(is_tnauth)

    def test_035_extract_identifier_info_for_challenge_success(self):
        """Test identifier extraction for challenge"""
        authz_info = {"identifier": {"type": "dns", "value": "example.com"}}

        id_type, id_value, is_wildcard = (
            self.business_logic.extract_identifier_info_for_challenge(authz_info)
        )

        self.assertEqual(id_type, "dns")
        self.assertEqual(id_value, "example.com")
        self.assertFalse(is_wildcard)

    def test_036_extract_identifier_info_for_challenge_no_identifier(self):
        """Test identifier extraction when no identifier present"""
        authz_info = {"status": "pending"}

        id_type, id_value, is_wildcard = (
            self.business_logic.extract_identifier_info_for_challenge(authz_info)
        )

        self.assertIsNone(id_type)
        self.assertIsNone(id_value)
        self.assertFalse(is_wildcard)

    def test_037_extract_identifier_info_for_challenge_partial_identifier(self):
        """Test identifier extraction with partial identifier info"""
        authz_info = {
            "identifier": {
                "type": "dns"
                # missing value
            }
        }

        id_type, id_value, is_wildcard = (
            self.business_logic.extract_identifier_info_for_challenge(authz_info)
        )

        self.assertEqual(id_type, "dns")
        self.assertIsNone(id_value)
        self.assertFalse(is_wildcard)

    def test_038_extract_identifier_info_for_challenge_wildcard_marker(self):
        """Test wildcard marker extraction independent from identifier value format."""
        authz_info = {
            "identifier": {"type": "dns", "value": "example.com"},
            "wildcard": True,
        }

        id_type, id_value, is_wildcard = (
            self.business_logic.extract_identifier_info_for_challenge(authz_info)
        )

        self.assertEqual(id_type, "dns")
        self.assertEqual(id_value, "example.com")
        self.assertTrue(is_wildcard)

    def test_039_is_authorization_eligible_for_expiry_valid(self):
        """Test eligibility check for valid authorization"""
        auth_record = {"name": "test_authz", "status__name": "valid"}

        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)

        self.assertTrue(result)

    def test_040_is_authorization_eligible_for_expiry_missing_name(self):
        """Test eligibility check with missing name"""
        auth_record = {"status__name": "valid"}

        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)

        self.assertFalse(result)

    def test_041_is_authorization_eligible_for_expiry_missing_status(self):
        """Test eligibility check with missing status"""
        auth_record = {"name": "test_authz"}

        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)

        self.assertFalse(result)

    def test_042_is_authorization_eligible_for_expiry_already_expired(self):
        """Test eligibility check for already expired authorization"""
        auth_record = {"name": "test_authz", "status__name": "expired"}

        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)

        self.assertFalse(result)

    def test_043_is_authorization_eligible_for_expiry_zero_expires(self):
        """Test eligibility check with zero expires"""
        auth_record = {"name": "test_authz", "status__name": "valid", "expires": 0}

        result = self.business_logic.is_authorization_eligible_for_expiry(auth_record)

        self.assertFalse(result)


class TestChallengeSetManager(unittest.TestCase):
    """Test ChallengeSetManager class"""

    def setUp(self):
        self.mock_logger = Mock()
        self.manager = ChallengeSetManager(
            debug=False, server_name="https://example.com", logger=self.mock_logger
        )

    def test_044_challenge_manager_initialization(self):
        """Test challenge manager initialization"""
        self.assertFalse(self.manager.debug)
        self.assertEqual(self.manager.server_name, "https://example.com")
        self.assertEqual(self.manager.logger, self.mock_logger)

    @patch("acme2certifier.acme_srv.authorization.Challenge")
    def test_045_get_challenge_set_for_authorization_success(
        self, mock_challenge_class
    ):
        """Test successful challenge set retrieval"""
        mock_challenge_instance = Mock()
        mock_challenge_instance.challengeset_get.return_value = [{"type": "http-01"}]
        mock_challenge_class.return_value.__enter__.return_value = (
            mock_challenge_instance
        )

        result = self.manager.get_challenge_set_for_authorization(
            authz_name="test_authz",
            status="pending",
            token="test_token",
            is_tnauth=False,
            expires=1234567890,
            id_type="dns",
            id_value="example.com",
        )

        self.assertEqual(result, [{"type": "http-01"}])
        mock_challenge_class.assert_called_once_with(
            debug=False,
            srv_name="https://example.com",
            logger=self.mock_logger,
            expiry=1234567890,
        )
        mock_challenge_instance.challengeset_get.assert_called_once_with(
            "test_authz",
            "pending",
            "test_token",
            False,
            "dns",
            "example.com",
            False,
        )

    @patch("acme2certifier.acme_srv.authorization.Challenge")
    def test_046_get_challenge_set_for_authorization_with_none_values(
        self, mock_challenge_class
    ):
        """Test challenge set retrieval with None id_type and id_value"""
        mock_challenge_instance = Mock()
        mock_challenge_instance.challengeset_get.return_value = []
        mock_challenge_class.return_value.__enter__.return_value = (
            mock_challenge_instance
        )

        result = self.manager.get_challenge_set_for_authorization(
            authz_name="test_authz",
            status="pending",
            token="test_token",
            is_tnauth=False,
            expires=1234567890,
        )

        self.assertEqual(result, [])
        mock_challenge_instance.challengeset_get.assert_called_once_with(
            "test_authz", "pending", "test_token", False, None, None, False
        )


class TestAuthorization(unittest.TestCase):
    def setUp(self):
        self.mock_logger = Mock()
        self.mock_message = Mock()
        self.authorization = Authorization(logger=self.mock_logger)

    def tearDown(self):
        pass

    def test_047_authorization_initialization_defaults(self):
        """Test Authorization initialization with defaults"""

        self.assertIsNone(self.authorization.server_name)
        self.assertFalse(self.authorization.debug)
        self.assertEqual(self.authorization.logger, self.mock_logger)
        self.assertIsInstance(self.authorization.config, AuthorizationConfiguration)
        self.assertIsInstance(self.authorization.repository, AuthorizationRepository)
        self.assertIsInstance(
            self.authorization.business_logic, AuthorizationBusinessLogic
        )
        self.assertIsInstance(self.authorization.challenge_manager, ChallengeSetManager)

    def test_048_authorization_initialization_custom_params(self):
        """Test Authorization initialization with custom parameters"""
        authorization = Authorization(
            debug=True, srv_name="https://example.com", logger=self.mock_logger
        )

        self.assertEqual(authorization.server_name, "https://example.com")
        self.assertTrue(authorization.debug)
        self.assertEqual(authorization.logger, self.mock_logger)

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_049_authorization_context_manager_enter(
        self, mock_load_config, mock_eab_profile
    ):
        """Test Authorization context manager enter"""
        mock_config_parser = ConfigParser()
        mock_config_parser.read_dict(
            {
                "Authorization": {"validity": "172800"},
                "Directory": {"url_prefix": "/custom"},
            }
        )
        mock_load_config.return_value = mock_config_parser

        result = self.authorization.__enter__()

        self.assertEqual(result, self.authorization)
        mock_load_config.assert_called_once()

    def test_050_authorization_context_manager_exit(self):
        """Test Authorization context manager exit"""
        # Should not raise any exceptions
        self.authorization.__exit__(None, None, None)

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_051_load_configuration_empty(self, mock_load_config, mock_eab_profile):
        """Test configuration loading with empty config"""
        mock_config = ConfigParser()
        mock_config.read_dict(
            {
                "Authorization": {"expiry_check_disable": "true"},
            }
        )
        mock_load_config.return_value = mock_config

        self.authorization._load_configuration()
        self.assertEqual(self.authorization.config.validity, 86400)  # default value
        self.assertTrue(self.authorization.config.expiry_check_disable)
        self.assertEqual(self.authorization.config.authz_path, "/acme/authz/")

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_052_load_configuration_success(self, mock_load_config, mock_eab_profile):
        """Test successful configuration loading"""
        mock_config = ConfigParser()
        mock_config.read_dict(
            {
                "Authorization": {
                    "validity": "172800",
                    "expiry_check_disable": "true",
                },
                "Directory": {"url_prefix": "/custom"},
            }
        )
        mock_load_config.return_value = mock_config

        self.authorization._load_configuration()

        self.assertEqual(self.authorization.config.validity, 172800)
        self.assertTrue(self.authorization.config.expiry_check_disable)
        self.assertEqual(self.authorization.config.authz_path, "/custom/acme/authz/")

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_053_load_configuration_caaidentities_csv_fallback(
        self, mock_load_config, mock_eab_profile
    ):
        """Test caaidentities fallback parsing from comma-separated string"""
        mock_config = ConfigParser()
        mock_config.read_dict(
            {
                "Directory": {"caaidentities": "acme.local, bar.local"},
            }
        )
        mock_load_config.return_value = mock_config

        self.authorization._load_configuration()

        self.assertEqual(
            self.authorization.config.caaidentities, ["acme.local", "bar.local"]
        )

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_054_load_configuration_caaidentities_json_array(
        self, mock_load_config, mock_eab_profile
    ):
        """Test caaidentities parsing from valid JSON array."""
        mock_config = ConfigParser()
        mock_config.read_dict(
            {
                "Directory": {"caaidentities": '["acme.local", "bar.local"]'},
            }
        )
        mock_load_config.return_value = mock_config

        self.authorization._load_configuration()

        self.assertEqual(
            self.authorization.config.caaidentities, ["acme.local", "bar.local"]
        )

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_055_load_configuration_caaidentities_json_non_array_warns(
        self, mock_load_config, mock_eab_profile
    ):
        """Test caaidentities non-array JSON falls back and logs warning."""
        raw_value = '{"issuer": "acme.local"}'
        mock_config = ConfigParser()
        mock_config.read_dict(
            {
                "Directory": {"caaidentities": raw_value},
            }
        )
        mock_load_config.return_value = mock_config

        self.authorization._load_configuration()

        self.assertEqual(self.authorization.config.caaidentities, [raw_value])
        self.mock_logger.warning.assert_any_call(
            "Failed to parse caaidentities from configuration, expected JSON array. Got: %s",
            raw_value,
        )

    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_056_load_configuration_invalid_validity(self, mock_load_config):
        """Test configuration loading with invalid validity"""
        mock_config = Mock()
        mock_config.get.side_effect = lambda section, key, fallback=None: {
            ("Authorization", "validity"): "invalid_number"
        }.get((section, key), fallback)
        mock_config.getboolean.return_value = False
        mock_load_config.return_value = mock_config

        with self.assertRaises(ConfigurationError) as context:
            self.authorization._load_configuration()

        self.assertIn(
            "Invalid validity parameter: invalid_number", str(context.exception)
        )

    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_057_load_configuration_empty_config(
        self, mock_load_config, mock_eab_profile
    ):
        """Test configuration loading with empty config"""
        mock_load_config.return_value = None

        self.authorization._load_configuration()

        # Should use defaults
        self.assertEqual(self.authorization.config.validity, 86400)
        self.assertFalse(self.authorization.config.expiry_check_disable)

    def test_058_get_authorization_details_not_found(self):
        """Test get_authorization_details when authorization not found"""
        # Replace repository with mock
        mock_repository = Mock()
        mock_repository.find_authorization_by_name.return_value = None
        self.authorization.repository = mock_repository

        result = self.authorization.get_authorization_details(
            "http://example.com/authz/test"
        )

        self.assertEqual(result, {})

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_059_get_authorization_details_success_minimal(self, mock_uts_to_date):
        """Test get_authorization_details with minimal success case"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"
        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()
        mock_challenge_manager = Mock()

        mock_repository.find_authorization_by_name.side_effect = [
            {"name": "test_authz"},  # First call (existence check)
            None,  # Second call (detailed lookup)
        ]
        mock_business_logic.extract_authorization_name_from_url.return_value = (
            "test_authz"
        )
        mock_business_logic.resolve_authorization_token_and_expiry.return_value = (
            "token",
            1234567890,
            True,
        )
        mock_business_logic.extract_identifier_info_for_challenge.return_value = (
            None,
            None,
            False,
        )
        mock_challenge_manager.get_challenge_set_for_authorization.return_value = []

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic
        self.authorization.challenge_manager = mock_challenge_manager

        result = self.authorization.get_authorization_details(
            "http://example.com/authz/test"
        )

        expected = {
            "expires": "2021-01-01T00:00:00Z",
            "status": "pending",
            "challenges": [],
        }
        self.assertEqual(result, expected)
        mock_repository.update_authorization_expiry.assert_called_once_with(
            "test_authz", "token", 1234567890
        )

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_059b_get_authorization_details_reuses_existing_token(
        self, mock_uts_to_date
    ):
        """Test get_authorization_details reuses stored token on subsequent fetch"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        mock_repository = Mock()
        mock_challenge_manager = Mock()

        mock_repository.find_authorization_by_name.side_effect = [
            {
                "name": "test_authz",
                "token": "existing_token",
                "expires": 1234567890,
            },
            None,
        ]
        mock_challenge_manager.get_challenge_set_for_authorization.return_value = []

        self.authorization.server_name = "http://example.com"
        self.authorization.config.authz_path = "/authz/"
        self.authorization.repository = mock_repository
        self.authorization.challenge_manager = mock_challenge_manager

        result = self.authorization.get_authorization_details(
            "http://example.com/authz/test_authz"
        )

        expected = {
            "expires": "2021-01-01T00:00:00Z",
            "status": "pending",
            "challenges": [],
        }
        self.assertEqual(result, expected)
        mock_repository.update_authorization_expiry.assert_not_called()
        mock_challenge_manager.get_challenge_set_for_authorization.assert_called_once_with(
            "test_authz",
            "pending",
            "existing_token",
            False,
            1234567890,
            None,
            None,
            False,
        )

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_060_get_authorization_details_success_with_details(self, mock_uts_to_date):
        """Test get_authorization_details with full details"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()
        mock_challenge_manager = Mock()

        auth_details = {"status__name": "valid", "type": "dns", "value": "example.com"}
        mock_repository.find_authorization_by_name.side_effect = [
            {"name": "test_authz"},  # First call
            auth_details,  # Second call
        ]
        mock_business_logic.extract_authorization_name_from_url.return_value = (
            "test_authz"
        )
        mock_business_logic.resolve_authorization_token_and_expiry.return_value = (
            "token",
            1234567890,
            True,
        )
        mock_business_logic.enrich_authorization_with_identifier_info.return_value = (
            {"status": "valid", "identifier": {"type": "dns", "value": "example.com"}},
            False,
        )
        mock_business_logic.extract_identifier_info_for_challenge.return_value = (
            "dns",
            "example.com",
            False,
        )
        mock_challenge_manager.get_challenge_set_for_authorization.return_value = [
            {"type": "http-01"}
        ]

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic
        self.authorization.challenge_manager = mock_challenge_manager

        result = self.authorization.get_authorization_details(
            "http://example.com/authz/test"
        )

        expected = {
            "expires": "2021-01-01T00:00:00Z",
            "status": "valid",
            "identifier": {"type": "dns", "value": "example.com"},
            "challenges": [{"type": "http-01"}],
        }
        self.assertEqual(result, expected)

    def test_061_get_authorization_details_challenge_error(self):
        """Test get_authorization_details when challenge creation fails"""
        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()
        mock_challenge_manager = Mock()

        mock_repository.find_authorization_by_name.side_effect = [
            {"name": "test_authz"},  # First call
            None,  # Second call
        ]
        mock_business_logic.extract_authorization_name_from_url.return_value = (
            "test_authz"
        )
        mock_business_logic.resolve_authorization_token_and_expiry.return_value = (
            "token",
            1234567890,
            True,
        )
        mock_business_logic.extract_identifier_info_for_challenge.return_value = (
            "dns",
            "example.com",
            False,
        )
        mock_challenge_manager.get_challenge_set_for_authorization.side_effect = (
            Exception("Challenge failed")
        )

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic
        self.authorization.challenge_manager = mock_challenge_manager

        result = self.authorization.get_authorization_details(
            "http://example.com/authz/test"
        )

        self.assertIsNone(result)
        self.mock_logger.error.assert_called()
        log_args = self.mock_logger.error.call_args[0]
        self.assertIn(
            "Failed to create challenge set for authorization", str(log_args[0])
        )
        self.assertIn("Challenge failed", str(log_args))

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_062_get_authorization_details_jit_validation_success(
        self, mock_uts_to_date
    ):
        """Test successful JIT dns-persist validation marks authorization valid"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        mock_repository = Mock()
        mock_business_logic = Mock()
        mock_challenge_manager = Mock()

        auth_details = {
            "status__name": "pending",
            "type": "dns",
            "value": "example.com",
            "order__name": "order_1",
            "order__account__name": "acct_1",
            "order__account__eab_kid": "kid_1",
        }
        mock_repository.find_authorization_by_name.side_effect = [
            {"name": "test_authz"},
            auth_details,
        ]
        mock_business_logic.extract_authorization_name_from_url.return_value = (
            "test_authz"
        )
        mock_business_logic.resolve_authorization_token_and_expiry.return_value = (
            "token",
            1234567890,
            True,
        )
        mock_business_logic.enrich_authorization_with_identifier_info.return_value = (
            {
                "status": "pending",
                "identifier": {"type": "dns", "value": "example.com"},
            },
            False,
        )
        mock_business_logic.extract_identifier_info_for_challenge.return_value = (
            "dns",
            "example.com",
            False,
        )

        self.authorization.server_name = "https://example.com"
        self.authorization.config.dns_persist_01_support = True
        self.authorization.config.dns_persist_jit_validation = True
        self.authorization.config.dns_persist_allow_policy_wildcard = True
        self.authorization.config.dns_server_list = ["1.1.1.1"]
        self.authorization.config.caaidentities = ["acme.local"]
        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic
        self.authorization.challenge_manager = mock_challenge_manager

        with patch("acme2certifier.acme_srv.authorization.ChallengeContext") as mock_context:
            with patch(
                "acme2certifier.acme_srv.authorization.DnsPersistChallengeValidator"
            ) as mock_validator_cls:
                context_obj = object()
                mock_context.return_value = context_obj
                validation_result = Mock(success=True, invalid=False)
                mock_validator = Mock()
                mock_validator.perform_validation.return_value = validation_result
                mock_validator_cls.return_value = mock_validator

                result = self.authorization.get_authorization_details(
                    "http://example.com/authz/test"
                )

        self.assertEqual(result["status"], "valid")
        self.assertEqual(result["challenges"], [])
        mock_repository.mark_authorization_as_valid.assert_called_once_with(
            "test_authz"
        )
        mock_repository.mark_order_as_ready.assert_called_once_with("order_1")
        mock_challenge_manager.get_challenge_set_for_authorization.assert_not_called()
        mock_context.assert_called_once()
        context_kwargs = mock_context.call_args.kwargs
        self.assertEqual(context_kwargs["authorization_type"], "dns")
        self.assertEqual(context_kwargs["authorization_value"], "example.com")
        self.assertEqual(
            context_kwargs["options"]["accounturi"],
            "https://example.com/acme/acct/acct_1",
        )
        self.assertEqual(
            context_kwargs["options"]["issuer_domain_names"], ["acme.local"]
        )

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_063_get_authorization_details_jit_validation_invalid_result_fallback(
        self, mock_uts_to_date
    ):
        """Test non-valid JIT result falls back to challenge generation"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        mock_repository = Mock()
        mock_business_logic = Mock()
        mock_challenge_manager = Mock()

        auth_details = {
            "status__name": "pending",
            "type": "dns",
            "value": "example.com",
            "order__name": "order_2",
            "order__account__name": None,
            "order__account__eab_kid": None,
        }
        mock_repository.find_authorization_by_name.side_effect = [
            {"name": "test_authz"},
            auth_details,
        ]
        mock_business_logic.extract_authorization_name_from_url.return_value = (
            "test_authz"
        )
        mock_business_logic.resolve_authorization_token_and_expiry.return_value = (
            "token",
            1234567890,
            True,
        )
        mock_business_logic.enrich_authorization_with_identifier_info.return_value = (
            {
                "status": "pending",
                "identifier": {"type": "dns", "value": "example.com"},
            },
            False,
        )
        mock_business_logic.extract_identifier_info_for_challenge.return_value = (
            "dns",
            "example.com",
            False,
        )
        mock_challenge_manager.get_challenge_set_for_authorization.return_value = [
            {"type": "dns-persist-01"}
        ]

        self.authorization.config.dns_persist_01_support = True
        self.authorization.config.dns_persist_jit_validation = True
        self.authorization.config.caaidentities = None
        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic
        self.authorization.challenge_manager = mock_challenge_manager

        with patch("acme2certifier.acme_srv.authorization.ChallengeContext") as mock_context:
            with patch(
                "acme2certifier.acme_srv.authorization.DnsPersistChallengeValidator"
            ) as mock_validator_cls:
                validation_result = Mock(success=True, invalid=True)
                mock_validator = Mock()
                mock_validator.perform_validation.return_value = validation_result
                mock_validator_cls.return_value = mock_validator

                result = self.authorization.get_authorization_details(
                    "http://example.com/authz/test"
                )

        self.assertEqual(result["status"], "pending")
        self.assertEqual(result["challenges"], [{"type": "dns-persist-01"}])
        mock_repository.mark_authorization_as_valid.assert_not_called()
        mock_repository.mark_order_as_ready.assert_not_called()
        mock_challenge_manager.get_challenge_set_for_authorization.assert_called_once()
        context_kwargs = mock_context.call_args.kwargs
        self.assertIsNone(context_kwargs["options"]["accounturi"])
        self.assertEqual(context_kwargs["options"]["issuer_domain_names"], [])

    @patch("acme2certifier.acme_srv.authorization.uts_to_date_utc")
    def test_064_get_authorization_details_jit_validation_exception_fallback(
        self, mock_uts_to_date
    ):
        """Test JIT validator exceptions are logged and fall back to challenge generation"""
        mock_uts_to_date.return_value = "2021-01-01T00:00:00Z"

        mock_repository = Mock()
        mock_business_logic = Mock()
        mock_challenge_manager = Mock()

        auth_details = {
            "status__name": "pending",
            "type": "dns",
            "value": "example.com",
            "order__name": "order_3",
            "order__account__name": "acct_3",
            "order__account__eab_kid": None,
        }
        mock_repository.find_authorization_by_name.side_effect = [
            {"name": "test_authz"},
            auth_details,
        ]
        mock_business_logic.extract_authorization_name_from_url.return_value = (
            "test_authz"
        )
        mock_business_logic.resolve_authorization_token_and_expiry.return_value = (
            "token",
            1234567890,
            True,
        )
        mock_business_logic.enrich_authorization_with_identifier_info.return_value = (
            {
                "status": "pending",
                "identifier": {"type": "dns", "value": "example.com"},
            },
            False,
        )
        mock_business_logic.extract_identifier_info_for_challenge.return_value = (
            "dns",
            "example.com",
            False,
        )
        mock_challenge_manager.get_challenge_set_for_authorization.return_value = [
            {"type": "dns-persist-01"}
        ]

        self.authorization.config.dns_persist_01_support = True
        self.authorization.config.dns_persist_jit_validation = True
        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic
        self.authorization.challenge_manager = mock_challenge_manager

        with patch("acme2certifier.acme_srv.authorization.ChallengeContext"):
            with patch(
                "acme2certifier.acme_srv.authorization.DnsPersistChallengeValidator"
            ) as mock_validator_cls:
                mock_validator = Mock()
                mock_validator.perform_validation.side_effect = Exception("jit failed")
                mock_validator_cls.return_value = mock_validator

                result = self.authorization.get_authorization_details(
                    "http://example.com/authz/test"
                )

        self.assertEqual(result["status"], "pending")
        self.assertEqual(result["challenges"], [{"type": "dns-persist-01"}])
        mock_challenge_manager.get_challenge_set_for_authorization.assert_called_once()
        self.mock_logger.error.assert_called()
        error_args = self.mock_logger.error.call_args[0]
        self.assertEqual(error_args[0], "JIT dns-persist-01 validation failed: %s")
        self.assertIn("jit failed", str(error_args[1]))

    @patch("acme2certifier.acme_srv.authorization.uts_now")
    def test_065_expire_invalid_authorizations_default_timestamp(self, mock_uts_now):
        """Test expire_invalid_authorizations with default timestamp"""
        mock_uts_now.return_value = 1234567890

        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()

        expired_authz = {"name": "expired_authz", "status__name": "valid"}
        mock_repository.search_expired_authorizations.return_value = [expired_authz]
        mock_business_logic.is_authorization_eligible_for_expiry.return_value = True

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic

        field_list, output_list = self.authorization.expire_invalid_authorizations()

        self.assertEqual(len(output_list), 1)
        self.assertEqual(output_list[0], expired_authz)
        mock_repository.mark_authorization_as_expired.assert_called_once_with(
            "expired_authz"
        )

    def test_066_expire_invalid_authorizations_custom_timestamp(self):
        """Test expire_invalid_authorizations with custom timestamp"""
        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()

        expired_authz = {"name": "expired_authz", "status__name": "valid"}
        mock_repository.search_expired_authorizations.return_value = [expired_authz]
        mock_business_logic.is_authorization_eligible_for_expiry.return_value = True

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic

        field_list, output_list = self.authorization.expire_invalid_authorizations(
            timestamp=1000000000
        )

        self.assertEqual(len(output_list), 1)
        mock_repository.search_expired_authorizations.assert_called_with(
            1000000000, field_list
        )

    def test_067_expire_invalid_authorizations_search_error(self):
        """Test expire_invalid_authorizations when search fails"""
        # Replace components with mocks
        mock_repository = Mock()
        mock_repository.search_expired_authorizations.side_effect = AuthorizationError(
            "Search failed"
        )
        self.authorization.repository = mock_repository

        field_list, output_list = self.authorization.expire_invalid_authorizations()

        self.assertEqual(field_list, [])
        self.assertEqual(len(output_list), 0)
        # Check that warning was called with the right pattern and message
        self.assertTrue(self.mock_logger.warning.called)
        call_args = self.mock_logger.warning.call_args[0]
        self.assertIn("Failed to search for expired authorizations", call_args[0])
        self.assertIsInstance(call_args[1], AuthorizationError)
        self.assertIn("Search failed", str(call_args[1]))

    def test_068_expire_invalid_authorizations_not_eligible(self):
        """Test expire_invalid_authorizations when authorization not eligible"""
        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()

        not_eligible_authz = {"name": "not_eligible", "status__name": "expired"}
        mock_repository.search_expired_authorizations.return_value = [
            not_eligible_authz
        ]
        mock_business_logic.is_authorization_eligible_for_expiry.return_value = False

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic

        field_list, output_list = self.authorization.expire_invalid_authorizations()

        self.assertEqual(len(output_list), 0)
        mock_repository.mark_authorization_as_expired.assert_not_called()

    def test_069_expire_invalid_authorizations_expire_error(self):
        """Test expire_invalid_authorizations when expiration fails"""

        # Replace components with mocks
        mock_repository = Mock()
        mock_business_logic = Mock()

        expired_authz = {"name": "expired_authz", "status__name": "valid"}
        mock_repository.search_expired_authorizations.return_value = [expired_authz]
        mock_business_logic.is_authorization_eligible_for_expiry.return_value = True
        mock_repository.mark_authorization_as_expired.side_effect = AuthorizationError(
            "Expire failed"
        )

        self.authorization.repository = mock_repository
        self.authorization.business_logic = mock_business_logic

        field_list, output_list = self.authorization.expire_invalid_authorizations()

        self.assertEqual(
            len(output_list), 1
        )  # Authorization is added before expiration fails
        # Check that warning was called with the right pattern and message
        self.assertTrue(self.mock_logger.warning.called)
        call_args = self.mock_logger.warning.call_args[0]
        self.assertIn("Failed to expire authorization", call_args[0])
        self.assertEqual(call_args[1], "expired_authz")
        self.assertIsInstance(call_args[2], AuthorizationError)
        self.assertIn("Expire failed", str(call_args[2]))

    def test_070_handle_get_request_success(self):
        """Test successful GET request handling"""
        auth_data = {"status": "valid", "expires": "2021-01-01T00:00:00Z"}
        with patch.object(
            self.authorization, "get_authorization_details"
        ) as mock_get_details:
            mock_get_details.return_value = auth_data

            result = self.authorization.handle_get_request(
                "http://example.com/authz/test"
            )

        expected = {"code": 200, "header": {}, "data": auth_data}
        self.assertEqual(result, expected)

    def test_071_handle_get_request_not_found(self):
        """Test GET request handling when authorization not found"""
        with patch.object(
            self.authorization, "get_authorization_details"
        ) as mock_get_details:
            mock_get_details.return_value = {}  # Empty result

            result = self.authorization.handle_get_request(
                "http://example.com/authz/test"
            )

        expected = {
            "code": 404,
            "header": {},
            "data": {"error": "Authorization not found"},
        }
        self.assertEqual(result, expected)

    def test_072_handle_get_request_none_result(self):
        """Test GET request handling when get_authorization_details returns None"""
        with patch.object(
            self.authorization, "get_authorization_details"
        ) as mock_get_details:
            mock_get_details.return_value = None

            result = self.authorization.handle_get_request(
                "http://example.com/authz/test"
            )

        expected = {
            "code": 404,
            "header": {},
            "data": {"error": "Authorization not found"},
        }
        self.assertEqual(result, expected)

    def test_073_handle_get_request_authorization_error(self):
        """Test GET request handling with authorization error"""
        with patch.object(
            self.authorization, "get_authorization_details"
        ) as mock_get_details:
            mock_get_details.side_effect = AuthorizationError("Test error")

            result = self.authorization.handle_get_request(
                "http://example.com/authz/test"
            )

        expected = {"code": 404, "header": {}, "data": {"error": "Test error"}}
        self.assertEqual(result, expected)
        self.mock_logger.error.assert_called()
        log_args = self.mock_logger.error.call_args[0]
        # Defensive: handle both tuple and non-tuple call_args
        if len(log_args) == 2:
            fmt, arg = log_args
            self.assertEqual(fmt, "Authorization error: %s")
            # Accept either the string or the exception instance
            if isinstance(arg, Exception):
                self.assertEqual(str(arg), "Test error")
            else:
                self.assertEqual(arg, "Test error")
            self.assertEqual(fmt % (str(arg),), "Authorization error: Test error")
        else:
            # fallback: check joined string
            self.assertIn("Authorization error", str(log_args))
            self.assertIn("Test error", str(log_args))

    def test_074_handle_post_request_success_with_expiry_check(self):
        """Test successful POST request handling with expiry check"""
        self.authorization.config.expiry_check_disable = False

        # Mock message check
        self.mock_message.check.return_value = (
            200,
            "OK",
            "",
            {"url": "http://example.com/authz/test"},
            {},
            "account",
        )

        # Mock invalidate
        with patch.object(self.authorization, "invalidate") as mock_invalidate:
            with patch.object(
                self.authorization, "get_authorization_details"
            ) as mock_get_details:
                auth_data = {"status": "valid"}
                mock_get_details.return_value = auth_data
                self.mock_message.prepare_response.return_value = {"final": "response"}

                result = self.authorization.handle_post_request('{"test": "content"}')

        mock_invalidate.assert_called_once()
        # Accept the actual error structure
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("code"), 400)
        self.assertIn("header", result)
        self.assertIn("data", result)
        self.assertEqual(result["data"].get("status"), 400)

    def test_075_handle_post_request_expiry_check_disabled(self):
        """Test POST request handling with expiry check disabled"""
        self.authorization.config.expiry_check_disable = True

        self.mock_message.check.return_value = (
            200,
            "OK",
            "",
            {"url": "http://example.com/authz/test"},
            {},
            "account",
        )

        with patch.object(self.authorization, "invalidate") as mock_invalidate:
            with patch.object(
                self.authorization, "get_authorization_details"
            ) as mock_get_details:
                mock_get_details.return_value = {"status": "valid"}
                self.mock_message.prepare_response.return_value = {"final": "response"}

                result = self.authorization.handle_post_request('{"test": "content"}')

        mock_invalidate.assert_not_called()

    def test_076_handle_post_request_invalidate_error(self):
        """Test POST request handling when invalidate fails"""
        self.mock_message.check.return_value = (
            200,
            "OK",
            "",
            {"url": "http://example.com/authz/test"},
            {},
            "account",
        )

        with patch.object(self.authorization, "invalidate") as mock_invalidate:
            mock_invalidate.side_effect = Exception("Invalidate failed")
            with patch.object(
                self.authorization, "get_authorization_details"
            ) as mock_get_details:
                mock_get_details.return_value = {"status": "valid"}
                self.mock_message.prepare_response.return_value = {"final": "response"}

                result = self.authorization.handle_post_request(
                    '{"test": "content_0067"}'
                )

        # Should continue processing despite invalidate error
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("code"), 400)
        self.assertIn("header", result)
        self.assertIn("data", result)
        self.assertEqual(result["data"].get("status"), 400)
        self.assertTrue(self.mock_logger.warning.called)
        call_args = self.mock_logger.warning.call_args[0]
        # Check the warning log message format and text
        self.assertEqual(call_args[0], "Failed to expire authorizations: %s")
        self.assertIn("Invalidate failed", str(call_args[1]))

    def test_077_handle_post_request_no_url(self):
        """Test POST request handling when mcheck returns no URL"""
        # Patch only the check method of the message
        with patch.object(
            self.mock_message,
            "check",
            return_value=(
                200,
                "OK",
                "",
                {"foo": "bar"},  # No "url" key
                {},
                "account",
            ),
        ):
            # Assign the mock_message to the authorization instance
            self.authorization.message = self.mock_message
            # Patch invalidate and get_authorization_details as before
            with patch.object(self.authorization, "invalidate") as mock_invalidate:
                with patch.object(
                    self.authorization, "get_authorization_details"
                ) as mock_get_details:
                    auth_data = {"status": "valid"}
                    mock_get_details.return_value = auth_data
                    # Mock prepare_response to check call arguments
                    with patch.object(
                        self.mock_message,
                        "prepare_response",
                        return_value={
                            "final": "response",
                            "code": 400,
                            "header": {"foo": "bar"},
                            "data": {"status": 400},
                        },
                    ) as mock_prepare_response:
                        result = self.authorization.handle_post_request(
                            '{"test": "content"}'
                        )

                        mock_prepare_response.assert_called_once()
                        # Check that prepare_response was called with the expected status_dic
                        called_args, called_kwargs = mock_prepare_response.call_args
                        # The second argument is the status_dic
                        status_dic = called_args[1]
                        expected_status_dic = {
                            "code": 400,
                            "type": "urn:ietf:params:acme:error:malformed",
                            "detail": "url is missing in protected",
                        }
                        self.assertEqual(status_dic, expected_status_dic)

            mock_invalidate.assert_called_once()
            # Accept the actual error structure
            self.assertIsInstance(result, dict)
            self.assertEqual(result.get("code"), 400)
            self.assertIn("header", result)
            self.assertIn("data", result)
            self.assertEqual(result["data"].get("status"), 400)

    def test_078_handle_post_request_message_check_failure(self):
        """Test POST request handling when message check fails"""
        self.mock_message.check.return_value = (
            400,
            "Bad Request",
            "Invalid message",
            {},
            {},
            "",
        )
        self.mock_message.prepare_response.return_value = {"error": "response"}

        result = self.authorization.handle_post_request('{"invalid": "content"}')

        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("code"), 400)
        self.assertIn("header", result)
        self.assertIn("data", result)
        self.assertEqual(result["data"].get("status"), 400)

    def test_079_handle_post_request_missing_url(self):
        """Test POST request handling with missing URL in protected"""
        # Patch check to return no 'url' in protected
        with patch.object(
            self.mock_message,
            "check",
            return_value=(
                200,
                "OK",
                "",
                {},
                {},
                "account",
            ),
        ):
            self.authorization.message = self.mock_message
            with patch.object(
                self.mock_message,
                "prepare_response",
                return_value={"error": "malformed"},
            ) as mock_prepare_response:
                result = self.authorization.handle_post_request('{"test": "content"}')
                mock_prepare_response.assert_called_once()
                status_dic = mock_prepare_response.call_args[0][1]
                expected_status_dic = {
                    "code": 400,
                    "type": "urn:ietf:params:acme:error:malformed",
                    "detail": "url is missing in protected",
                }
                self.assertEqual(status_dic, expected_status_dic)
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("error"), "malformed")

    def test_080_handle_post_request_authorization_lookup_failed(self):
        """Test POST request handling when authorization lookup fails"""
        # Patch check to return a valid url in protected
        with patch.object(
            self.mock_message,
            "check",
            return_value=(
                200,
                "OK",
                "",
                {"url": "http://example.com/authz/test"},
                {},
                "account",
            ),
        ):
            self.authorization.message = self.mock_message
            with patch.object(
                self.authorization, "get_authorization_details", return_value={}
            ) as mock_get_details:
                with patch.object(
                    self.mock_message,
                    "prepare_response",
                    return_value={"error": "unauthorized"},
                ) as mock_prepare_response:
                    result = self.authorization.handle_post_request(
                        '{"test": "content"}'
                    )
                    mock_prepare_response.assert_called_once()
                    status_dic = mock_prepare_response.call_args[0][1]
                    expected_status_dic = {
                        "code": 403,
                        "type": "urn:ietf:params:acme:error:unauthorized",
                        "detail": "authorization lookup failed",
                    }
                    self.assertEqual(status_dic, expected_status_dic)
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("error"), "unauthorized")

    def test_081_handle_post_request_authorization_error(self):
        """Test POST request handling when authorization error occurs"""
        # Patch check to return a valid url in protected
        with patch.object(
            self.mock_message,
            "check",
            return_value=(
                200,
                "OK",
                "",
                {"url": "http://example.com/authz/test"},
                {},
                "account",
            ),
        ):
            self.authorization.message = self.mock_message
            with patch.object(
                self.authorization,
                "get_authorization_details",
                side_effect=AuthorizationError("Auth error"),
            ) as mock_get_details:
                with patch.object(
                    self.mock_message,
                    "prepare_response",
                    return_value={"error": "unauthorized"},
                ) as mock_prepare_response:
                    result = self.authorization.handle_post_request(
                        '{"test": "content"}'
                    )
                    mock_prepare_response.assert_called_once()
                    status_dic = mock_prepare_response.call_args[0][1]
                    expected_status_dic = {
                        "code": 403,
                        "type": "urn:ietf:params:acme:error:unauthorized",
                        "detail": "authorization error",
                    }
                    self.assertEqual(status_dic, expected_status_dic)
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("error"), "unauthorized")

    def test_082_handle_post_request_authorization_details_valid(self):
        """Test POST request handling when get_authorization_details returns something valid"""
        # Patch check to return a valid url in protected
        with patch.object(
            self.mock_message,
            "check",
            return_value=(
                200,
                "message",
                "detail",
                {"url": "http://example.com/authz/test"},
                {},
                "account",
            ),
        ):
            self.authorization.message = self.mock_message
            with patch.object(
                self.authorization,
                "get_authorization_details",
                return_value={"foo": "bar"},
            ) as mock_get_details:
                with patch.object(
                    self.mock_message,
                    "prepare_response",
                    return_value={"error": "unauthorized"},
                ) as mock_prepare_response:
                    result = self.authorization.handle_post_request(
                        '{"test": "content"}'
                    )
                    mock_prepare_response.assert_called_once()
                    status_dic = mock_prepare_response.call_args[0][1]
                    expected_status_dic = {
                        "code": 200,
                        "type": "message",
                        "detail": "detail",
                    }
                    self.assertEqual(status_dic, expected_status_dic)
        self.assertIsInstance(result, dict)
        self.assertEqual(result.get("error"), "unauthorized")

    def test_083_new_get_backward_compatibility(self):
        """Test new_get backward compatibility method"""
        with patch.object(self.authorization, "handle_get_request") as mock_handle_get:
            mock_handle_get.return_value = {"code": 200}
            result = self.authorization.new_get("http://example.com/authz/test")
        self.assertEqual(result, {"code": 200})
        mock_handle_get.assert_called_once_with("http://example.com/authz/test")

    def test_084_new_post_backward_compatibility(self):
        """Test new_post backward compatibility method"""
        with patch.object(
            self.authorization, "handle_post_request"
        ) as mock_handle_post:
            mock_handle_post.return_value = {"code": 200}
            result = self.authorization.new_post('{"test": "content"}')
        self.assertEqual(result, {"code": 200})
        mock_handle_post.assert_called_once_with('{"test": "content"}')

    def test_085_invalidate_backward_compatibility(self):
        """Test invalidate backward compatibility method"""
        with patch.object(
            self.authorization, "expire_invalid_authorizations"
        ) as mock_expire:
            mock_expire.return_value = (["field"], ["output"])

            result = self.authorization.invalidate(timestamp=1000000000)
        self.assertEqual(result, (["field"], ["output"]))
        mock_expire.assert_called_once_with(1000000000)

    @patch(
        "acme2certifier.acme_srv.authorization.config_dns_server_list_load", return_value=(False, None)
    )
    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_086_load_configuration_prevalidated_domainlist_success(
        self, mock_load_config, mock_eab_profile, mock_dns_list
    ):
        """Test prevalidated_domainlist loads and logger warning is called"""
        mock_config = Mock()
        domainlist = ["example.com", "test.com"]
        mock_config.get.side_effect = lambda section, key, fallback=None: (
            json.dumps(domainlist)
            if (section, key) == ("Authorization", "prevalidated_domainlist")
            else fallback
        )
        mock_config.getboolean.return_value = False
        mock_load_config.return_value = mock_config
        self.authorization._load_configuration()
        self.assertEqual(self.authorization.config.prevalidated_domainlist, domainlist)
        self.mock_logger.warning.assert_called()
        call_args = self.mock_logger.warning.call_args[0]
        self.assertIn(
            "prevalidated_domainlist loaded globally. Such configuration is NOT recommended as this is a severe security risk!",
            str(call_args[0]),
        )

    def test_087_apply_prevalidation_whitelist_else_branch(self):
        """Test _apply_prevalidation_whitelist else branch when auth_details is None and domain is whitelisted."""
        self.authorization.config.prevalidated_domainlist = ["example.com"]

        # Patch is_domain_whitelisted to always return True
        import acme2certifier.acme_srv.helpers.domain_utils as domain_utils

        orig_is_domain_whitelisted = domain_utils.is_domain_whitelisted
        domain_utils.is_domain_whitelisted = lambda logger, domain, whitelist: True

        # Patch repository methods to track calls
        self.authorization.repository.mark_authorization_as_valid = types.MethodType(
            lambda self, name: setattr(self, "_valid_called", name),
            self.authorization.repository,
        )
        self.authorization.repository.mark_order_as_ready = types.MethodType(
            lambda self, order_name: setattr(self, "_order_ready_called", order_name),
            self.authorization.repository,
        )

        # Prepare inputs
        authz_name = "authz1"
        auth_details = None  # triggers the else branch
        id_type = "dns"
        id_value = "example.com"
        authz_info = {"status": "pending"}

        # Call method
        self.authorization._apply_prevalidation_whitelist(
            authz_name, auth_details, id_type, id_value, authz_info
        )

        # Check that status is set to valid
        self.assertEqual(authz_info["status"], "valid")
        # Check that mark_authorization_as_valid was called
        self.assertEqual(
            getattr(self.authorization.repository, "_valid_called", None), authz_name
        )
        # Check that mark_order_as_ready was NOT called (since auth_details is None)
        self.assertFalse(hasattr(self.authorization.repository, "_order_ready_called"))

        # Clean up
        domain_utils.is_domain_whitelisted = orig_is_domain_whitelisted

    def test_087b_apply_prevalidation_whitelist_missing_id_value(self):
        """Test _apply_prevalidation_whitelist skips all branches when id_value is None."""
        self.authorization.config.email_identifier_rewrite = True
        self.authorization.config.prevalidated_emaillist = ["user@example.com"]
        self.authorization.config.prevalidated_domainlist = ["*"]
        self.authorization.config.prevalidated_iplist = ["0.0.0.0/0"]
        self.authorization.repository = Mock()

        for id_type in ("email", "dns", "ip"):
            authz_info = {"status": "pending"}
            self.authorization._apply_prevalidation_whitelist(
                f"authz_{id_type}",
                {"order__name": "order_1"},
                id_type,
                None,
                authz_info,
            )
            self.assertEqual(authz_info["status"], "pending")
            self.authorization.repository.mark_authorization_as_valid.assert_not_called()
            self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_088_handle_domain_prevalidation_wildcard_identifier_matches_policy(self):
        """Wildcard identifiers normalized to base domain should still match wildcard whitelist entries."""
        self.authorization.config.prevalidated_domainlist = ["*.bar.local"]
        self.authorization.repository.mark_authorization_as_valid = Mock()
        self.authorization.repository.mark_order_as_ready = Mock()
        self.mock_logger.reset_mock()

        authz_info = {"status": "pending", "wildcard": True}
        auth_details = {"order__name": "order_1"}

        self.authorization._handle_domain_prevalidation(
            "authz_1", auth_details, "bar.local", authz_info
        )

        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            "authz_1"
        )
        self.authorization.repository.mark_order_as_ready.assert_called_once_with(
            "order_1"
        )

        debug_messages = [
            str(call.args[0])
            for call in self.mock_logger.debug.call_args_list
            if call.args
        ]
        self.assertTrue(
            any(
                "Evaluating domain whitelist match for id_value" in msg
                for msg in debug_messages
            )
        )
        self.assertTrue(
            any("Whitelist check result for id_value" in msg for msg in debug_messages)
        )
        self.assertTrue(
            any(
                "Reconstructed wildcard id_value for whitelist check" in msg
                for msg in debug_messages
            )
        )
        self.assertTrue(
            any(
                "Whitelist check result for reconstructed wildcard id_value" in msg
                for msg in debug_messages
            )
        )

    def test_089_handle_domain_prevalidation_global_wildcard_with_order(self):
        """Singleton wildcard list should immediately validate and mark order ready."""
        self.authorization.config.prevalidated_domainlist = ["*"]
        self.authorization.repository.mark_authorization_as_valid = Mock()
        self.authorization.repository.mark_order_as_ready = Mock()
        self.mock_logger.reset_mock()

        authz_info = {"status": "pending"}
        auth_details = {"order__name": "order_wildcard"}

        self.authorization._handle_domain_prevalidation(
            "authz_global", auth_details, "any.domain", authz_info
        )

        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            "authz_global"
        )
        self.authorization.repository.mark_order_as_ready.assert_called_once_with(
            "order_wildcard"
        )
        self.mock_logger.warning.assert_called()

    def test_090_handle_domain_prevalidation_global_wildcard_without_order(self):
        """Singleton wildcard list should validate even without order details and not call mark_order_as_ready."""
        self.authorization.config.prevalidated_domainlist = [" * "]
        self.authorization.repository.mark_authorization_as_valid = Mock()
        self.authorization.repository.mark_order_as_ready = Mock()
        self.mock_logger.reset_mock()

        authz_info = {"status": "pending"}

        self.authorization._handle_domain_prevalidation(
            "authz_global_no_order", None, "any.domain", authz_info
        )

        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            "authz_global_no_order"
        )
        self.authorization.repository.mark_order_as_ready.assert_not_called()
        self.mock_logger.debug.assert_any_call(
            "No order information found for authorization %s",
            "authz_global_no_order",
        )

    def test_091_handle_domain_prevalidation_mixed_list_no_global_wildcard(self):
        """Wildcard-all branch must not trigger for mixed lists containing '*' plus other entries."""
        self.authorization.config.prevalidated_domainlist = ["*", "example.com"]
        self.authorization.repository.mark_authorization_as_valid = Mock()
        self.authorization.repository.mark_order_as_ready = Mock()
        self.mock_logger.reset_mock()

        authz_info = {"status": "pending", "wildcard": False}
        auth_details = {"order__name": "order_mixed"}

        with patch("acme2certifier.acme_srv.authorization.is_domain_whitelisted", return_value=False):
            self.authorization._handle_domain_prevalidation(
                "authz_mixed", auth_details, "unlisted.example.net", authz_info
            )

        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()
        warning_messages = [
            str(call.args[0])
            for call in self.mock_logger.warning.call_args_list
            if call.args
        ]
        self.assertFalse(
            any(
                "Global wildcard prevalidation is active" in msg
                for msg in warning_messages
            )
        )

    def test_092_handle_domain_prevalidation_non_wildcard_not_broadened(self):
        """Non-wildcard identifiers should not be auto-matched by wildcard-only policy."""
        self.authorization.config.prevalidated_domainlist = ["*.bar.local"]
        self.authorization.repository.mark_authorization_as_valid = Mock()
        self.authorization.repository.mark_order_as_ready = Mock()
        self.mock_logger.reset_mock()

        authz_info = {"status": "pending", "wildcard": False}
        auth_details = {"order__name": "order_1"}

        self.authorization._handle_domain_prevalidation(
            "authz_1", auth_details, "bar.local", authz_info
        )

        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

        debug_messages = [
            str(call.args[0])
            for call in self.mock_logger.debug.call_args_list
            if call.args
        ]
        self.assertTrue(
            any(
                "Evaluating domain whitelist match for id_value" in msg
                for msg in debug_messages
            )
        )
        self.assertTrue(
            any("Whitelist check result for id_value" in msg for msg in debug_messages)
        )
        self.assertTrue(
            any(
                "Skipping reconstructed wildcard whitelist check" in msg
                for msg in debug_messages
            )
        )

    def test_093_apply_eab_and_prevalidation_whitelist_always_calls_domain_whitelist(
        self,
    ):
        """Test that _apply_eab_and_prevalidation_whitelist always calls _apply_prevalidation_whitelist, regardless of EAB profile logic."""
        # Patch _apply_prevalidation_whitelist to track calls
        with patch.object(
            self.authorization, "_apply_prevalidation_whitelist"
        ) as mock_domain_whitelist:
            # EAB profiling off
            self.authorization.config.eab_profiling = False
            self.authorization._apply_eab_and_prevalidation_whitelist(
                "authz", {}, "dns", "foo.com", {}
            )
            mock_domain_whitelist.assert_called_once_with(
                "authz", {}, "dns", "foo.com", {}
            )

        with patch.object(
            self.authorization, "_apply_prevalidation_whitelist"
        ) as mock_domain_whitelist:
            # EAB profiling on, but no eab_handler
            self.authorization.config.eab_profiling = True
            self.authorization.config.eab_handler = None
            self.authorization._apply_eab_and_prevalidation_whitelist(
                "authz", {}, "dns", "foo.com", {}
            )
            mock_domain_whitelist.assert_called_once_with(
                "authz", {}, "dns", "foo.com", {}
            )

    @patch(
        "acme2certifier.acme_srv.authorization.config_dns_server_list_load", return_value=(False, None)
    )
    @patch("acme2certifier.acme_srv.authorization.config_eab_profile_load", return_value=(False, None))
    @patch("acme2certifier.acme_srv.authorization.load_config")
    def test_094_load_configuration_prevalidated_domainlist_invalid_json(
        self, mock_load_config, mock_eab_profile, mock_dns_list
    ):
        """Test prevalidated_domainlist with invalid JSON raises ConfigurationError and sets None"""
        mock_config = Mock()
        mock_config.get.side_effect = lambda section, key, fallback=None: (
            "not-a-json"
            if (section, key) == ("Authorization", "prevalidated_domainlist")
            else fallback
        )
        mock_config.getboolean.return_value = False
        mock_load_config.return_value = mock_config

        with self.assertRaises(ConfigurationError) as context:
            self.authorization._load_configuration()
        self.assertIn(
            "Invalid prevalidated_domainlist parameter", str(context.exception)
        )
        self.assertIsNone(self.authorization.config.prevalidated_domainlist)

    def test_095_eab_profile_prevalidated_domainlist_applied(self):
        """Test EAB profile sets prevalidated_domainlist from profile"""
        self.authorization.config.eab_profiling = True
        profile_dic = {
            "kid": {"authorization": {"prevalidated_domainlist": ["foo.com"]}}
        }
        mock_context = Mock()
        mock_context.key_file_load.return_value = profile_dic
        mock_context.__enter__ = Mock(return_value=mock_context)
        mock_context.__exit__ = Mock(return_value=None)
        mock_eab_handler_class = Mock(return_value=mock_context)
        self.authorization.config.eab_handler = mock_eab_handler_class
        auth_details = {"order__account__eab_kid": "kid"}
        # Should set prevalidated_domainlist
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", auth_details, "dns", "foo.com", {}
        )
        self.assertEqual(self.authorization.config.prevalidated_domainlist, ["foo.com"])

    def test_096_eab_profile_no_prevalidated_domainlist(self):
        """Test EAB profile present but no prevalidated_domainlist in profile"""
        self.authorization.config.eab_profiling = True
        profile_dic = {"kid": {"authorization": {}}}
        mock_context = Mock()
        mock_context.key_file_load.return_value = profile_dic
        mock_context.__enter__ = Mock(return_value=mock_context)
        mock_context.__exit__ = Mock(return_value=None)
        mock_eab_handler_class = Mock(return_value=mock_context)
        self.authorization.config.eab_handler = mock_eab_handler_class
        auth_details = {"order__account__eab_kid": "kid"}
        # Should not set prevalidated_domainlist
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", auth_details, "dns", "foo.com", {}
        )
        self.assertIsNone(self.authorization.config.prevalidated_domainlist)

    def test_097_eab_profile_handler_exception(self):
        """Test EAB profile handler raises exception, logger.error called with correct message"""
        self.authorization.config.eab_profiling = True
        mock_context = MagicMock()
        mock_context.__enter__.side_effect = Exception("fail")
        mock_eab_handler_class = Mock(return_value=mock_context)
        self.authorization.config.eab_handler = mock_eab_handler_class
        auth_details = {"order__account__eab_kid": "kid"}
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", auth_details, "dns", "foo.com", {}
        )
        self.mock_logger.error.assert_called()
        log_args = self.mock_logger.error.call_args[0]
        self.assertIn("Failed to process EAB profile for challenge", str(log_args[0]))
        self.assertIn("authz", str(log_args))
        self.assertIn("kid", str(log_args))
        self.assertIn("fail", str(log_args))

    def test_098_domain_whitelist_dns_match(self):
        """Test DNS identifier matches prevalidated_domainlist, status set to valid, mark methods called"""
        self.authorization.config.prevalidated_domainlist = ["foo.com"]
        self.authorization.repository = Mock()
        authz_info = {"status": "pending"}
        with patch("acme2certifier.acme_srv.authorization.is_domain_whitelisted", return_value=True):
            self.authorization._apply_eab_and_prevalidation_whitelist(
                "authz", {"order__name": "order1"}, "dns", "foo.com", authz_info
            )
        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            "authz"
        )
        self.authorization.repository.mark_order_as_ready.assert_called_once_with(
            "order1"
        )

    def test_099_domain_whitelist_dns_no_match(self):
        """Test DNS identifier does not match prevalidated_domainlist, status not changed, no mark calls"""
        self.authorization.config.prevalidated_domainlist = ["foo.com"]
        self.authorization.repository = Mock()
        authz_info = {"status": "pending"}
        with patch("acme2certifier.acme_srv.authorization.is_domain_whitelisted", return_value=False):
            self.authorization._apply_eab_and_prevalidation_whitelist(
                "authz", {"order__name": "order1"}, "dns", "bar.com", authz_info
            )
        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_100_domain_whitelist_not_set(self):
        """Test prevalidated_domainlist not set, nothing happens"""
        self.authorization.config.prevalidated_domainlist = None
        self.authorization.repository = Mock()
        authz_info = {"status": "pending"}
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", {"order__name": "order1"}, "dns", "foo.com", authz_info
        )
        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_101_domain_whitelist_non_dns(self):
        """Test non-dns identifier, nothing happens"""
        self.authorization.config.prevalidated_domainlist = ["foo.com"]
        self.authorization.repository = Mock()
        authz_info = {"status": "pending"}
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", {"order__name": "order1"}, "email", "foo@bar.com", authz_info
        )
        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    @patch("acme2certifier.acme_srv.helpers.domain_utils.is_ip_whitelisted", return_value=True)
    def test_102_ip_prevalidation_status_set_and_logs(self, mock_ip_whitelisted):
        """If IP is whitelisted, status is set to valid, mark_authorization_as_valid called, logs info"""
        authz_name = "authz_ip"
        auth_details = {"order__name": "order_ip"}
        id_type = "ip"
        id_value = "192.168.1.1"
        authz_info = {"status": "pending"}

        self.authorization.config.prevalidated_iplist = ["192.168.1.1", "10.0.0.0/8"]
        self.authorization.repository = Mock()

        self.authorization._apply_prevalidation_whitelist(
            authz_name, auth_details, id_type, id_value, authz_info
        )

        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            authz_name
        )
        self.authorization.repository.mark_order_as_ready.assert_called_once_with(
            "order_ip"
        )
        self.mock_logger.debug.assert_any_call(
            "IP %s is preauthorized, setting authorization status to 'valid'", id_value
        )

    @patch("acme2certifier.acme_srv.helpers.domain_utils.is_ip_whitelisted", return_value=True)
    def test_103_ip_prevalidation_status_set_no_auth_details_and_logs(
        self, mock_ip_whitelisted
    ):
        """If IP is whitelisted and auth_details is None, status is set to valid, only mark_authorization_as_valid called, logs info"""
        authz_name = "authz_ip"
        auth_details = None
        id_type = "ip"
        id_value = "10.0.0.1"
        authz_info = {"status": "pending"}

        self.authorization.config.prevalidated_iplist = ["192.168.1.1", "10.0.0.0/8"]
        self.authorization.repository = Mock()

        self.authorization._apply_prevalidation_whitelist(
            authz_name, auth_details, id_type, id_value, authz_info
        )

        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            authz_name
        )
        self.authorization.repository.mark_order_as_ready.assert_not_called()
        self.mock_logger.debug.assert_any_call(
            "IP %s is preauthorized, setting authorization status to 'valid'", id_value
        )

    @patch("acme2certifier.acme_srv.helpers.domain_utils.is_ip_whitelisted", return_value=False)
    def test_104_ip_prevalidation_not_whitelisted(self, mock_ip_whitelisted):
        """If IP is not whitelisted, status remains unchanged, no mark calls, logs debug"""
        authz_name = "authz_ip"
        auth_details = {"order__name": "order_ip"}
        id_type = "ip"
        id_value = "203.0.113.5"
        authz_info = {"status": "pending"}

        self.authorization.config.prevalidated_iplist = ["192.168.1.1", "10.0.0.0/8"]
        self.authorization.repository = Mock()

        self.authorization._apply_prevalidation_whitelist(
            authz_name, auth_details, id_type, id_value, authz_info
        )
        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()
        self.mock_logger.debug.assert_any_call(
            "IP %s is not preauthorized, leaving authorization status unchanged",
            id_value,
        )

    def test_105_ip_prevalidation_iplist_not_set(self):
        """If prevalidated_iplist is None, nothing happens"""
        self.authorization.config.prevalidated_iplist = None
        authz_name = "authz_ip"
        auth_details = {"order__name": "order_ip"}
        id_type = "ip"
        id_value = "192.168.1.1"
        authz_info = {"status": "pending"}

        self.authorization.repository = Mock()

        self.authorization._apply_prevalidation_whitelist(
            authz_name, auth_details, id_type, id_value, authz_info
        )

        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_106_ip_prevalidation_wrong_id_type(self):
        """If id_type is not 'ip', nothing happens"""
        authz_name = "authz_ip"
        auth_details = {"order__name": "order_ip"}
        id_type = "dns"
        id_value = "example.com"
        authz_info = {"status": "pending"}

        self.authorization.config.prevalidated_iplist = ["192.168.1.1", "10.0.0.0/8"]
        self.authorization.repository = Mock()

        self.authorization._apply_prevalidation_whitelist(
            authz_name, auth_details, id_type, id_value, authz_info
        )

        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_107_eab_profile_sets_prevalidated_iplist(self):
        """Test EAB profile sets prevalidated_iplist from profile and logs debug message"""
        self.authorization.config.eab_profiling = True
        profile_dic = {
            "kid": {
                "authorization": {"prevalidated_iplist": ["10.1.1.1", "10.2.0.0/16"]}
            }
        }
        mock_context = Mock()
        mock_context.key_file_load.return_value = profile_dic
        mock_context.__enter__ = Mock(return_value=mock_context)
        mock_context.__exit__ = Mock(return_value=None)
        mock_eab_handler_class = Mock(return_value=mock_context)
        self.authorization.config.eab_handler = mock_eab_handler_class
        auth_details = {"order__account__eab_kid": "kid"}
        # Should set prevalidated_iplist
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", auth_details, "ip", "10.1.1.1", {}
        )
        self.assertEqual(
            self.authorization.config.prevalidated_iplist, ["10.1.1.1", "10.2.0.0/16"]
        )
        self.mock_logger.debug.assert_any_call(
            "Authorization._apply_eab_and_domain_whitelist() - apply prevalidated_iplist from eab profile."
        )

    def test_108_eab_profile_no_prevalidated_iplist(self):
        """Test EAB profile present but no prevalidated_iplist in profile (should not set or log)"""
        self.authorization.config.eab_profiling = True
        profile_dic = {"kid": {"authorization": {}}}
        mock_context = Mock()
        mock_context.key_file_load.return_value = profile_dic
        mock_context.__enter__ = Mock(return_value=mock_context)
        mock_context.__exit__ = Mock(return_value=None)
        mock_eab_handler_class = Mock(return_value=mock_context)
        self.authorization.config.eab_handler = mock_eab_handler_class
        auth_details = {"order__account__eab_kid": "kid"}
        # Should not set prevalidated_iplist
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", auth_details, "ip", "10.1.1.1", {}
        )
        self.assertIsNone(self.authorization.config.prevalidated_iplist)

    def test_109_handle_email_prevalidation_email_whitelisted(self):
        """Test _handle_email_prevalidation sets status to valid and calls mark methods when email is whitelisted."""
        self.authorization.config.prevalidated_emaillist = ["user@example.com"]
        self.authorization.repository = Mock()
        authz_name = "authz_email"
        auth_details = {"order__name": "order_email"}
        id_value = "user@example.com"
        authz_info = {"status": "pending"}
        with patch("acme2certifier.acme_srv.authorization.is_email_whitelisted", return_value=True):
            self.authorization._handle_email_prevalidation(
                authz_name, auth_details, id_value, authz_info
            )
        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            authz_name
        )
        self.authorization.repository.mark_order_as_ready.assert_called_once_with(
            "order_email"
        )

    def test_110_handle_email_prevalidation_email_not_whitelisted(self):
        """Test _handle_email_prevalidation does not change status or call mark methods when email is not whitelisted."""
        self.authorization.config.prevalidated_emaillist = ["user@example.com"]
        self.authorization.repository = Mock()
        authz_name = "authz_email"
        auth_details = {"order__name": "order_email"}
        id_value = "other@example.com"
        authz_info = {"status": "pending"}
        with patch("acme2certifier.acme_srv.authorization.is_email_whitelisted", return_value=False):
            self.authorization._handle_email_prevalidation(
                authz_name, auth_details, id_value, authz_info
            )
        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_111_handle_email_prevalidation_empty_emaillist(self):
        """Test _handle_email_prevalidation does nothing if prevalidated_emaillist is None or empty."""
        self.authorization.config.prevalidated_emaillist = None
        self.authorization.repository = Mock()
        authz_name = "authz_email"
        auth_details = {"order__name": "order_email"}
        id_value = "user@example.com"
        authz_info = {"status": "pending"}
        self.authorization._handle_email_prevalidation(
            authz_name, auth_details, id_value, authz_info
        )
        self.assertEqual(authz_info["status"], "pending")
        self.authorization.repository.mark_authorization_as_valid.assert_not_called()
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_112_email_prevalidation_no_auth_details(self):
        """Test _handle_email_prevalidation sets status to valid and only calls mark_authorization_as_valid if auth_details is None."""
        self.authorization.config.prevalidated_emaillist = ["user@example.com"]
        self.authorization.repository = Mock()
        authz_name = "authz_email"
        auth_details = None
        id_value = "user@example.com"
        authz_info = {"status": "pending"}
        with patch("acme2certifier.acme_srv.authorization.is_email_whitelisted", return_value=True):
            self.authorization._handle_email_prevalidation(
                authz_name, auth_details, id_value, authz_info
            )
        self.assertEqual(authz_info["status"], "valid")
        self.authorization.repository.mark_authorization_as_valid.assert_called_once_with(
            authz_name
        )
        self.authorization.repository.mark_order_as_ready.assert_not_called()

    def test_113_eab_profile_sets_prevalidated_emaillist(self):
        """Test EAB profile sets prevalidated_emaillist from profile and logs debug message."""
        self.authorization.config.eab_profiling = True
        profile_dic = {
            "kid": {
                "authorization": {
                    "prevalidated_emaillist": ["foo@example.com", "bar@example.com"]
                }
            }
        }
        mock_context = Mock()
        mock_context.key_file_load.return_value = profile_dic
        mock_context.__enter__ = Mock(return_value=mock_context)
        mock_context.__exit__ = Mock(return_value=None)
        mock_eab_handler_class = Mock(return_value=mock_context)
        self.authorization.config.eab_handler = mock_eab_handler_class
        auth_details = {"order__account__eab_kid": "kid"}
        # Should set prevalidated_emaillist
        self.authorization._apply_eab_and_prevalidation_whitelist(
            "authz", auth_details, "email", "foo@example.com", {}
        )
        self.assertEqual(
            self.authorization.config.prevalidated_emaillist,
            ["foo@example.com", "bar@example.com"],
        )
        self.mock_logger.debug.assert_any_call(
            "Authorization._apply_eab_and_domain_whitelist() - apply prevalidated_emaillist from eab profile."
        )


class TestAuthorizationExceptions(unittest.TestCase):
    # Test custom exception classes

    def test_114_authorization_error(self):
        """Test AuthorizationError exception"""
        with self.assertRaises(AuthorizationError) as context:
            raise AuthorizationError("Test error message")
        self.assertEqual(str(context.exception), "Test error message")

    def test_115_authorization_not_found_error(self):
        """Test AuthorizationNotFoundError exception"""
        with self.assertRaises(AuthorizationNotFoundError) as context:
            raise AuthorizationNotFoundError("Authorization not found")
        self.assertEqual(str(context.exception), "Authorization not found")
        self.assertIsInstance(context.exception, AuthorizationError)

    def test_116_authorization_expired_error(self):
        """Test AuthorizationExpiredError exception"""
        with self.assertRaises(AuthorizationExpiredError) as context:
            raise AuthorizationExpiredError("Authorization expired")
        self.assertEqual(str(context.exception), "Authorization expired")
        self.assertIsInstance(context.exception, AuthorizationError)

    def test_117_configuration_error(self):
        """Test ConfigurationError exception"""
        with self.assertRaises(ConfigurationError) as context:
            raise ConfigurationError("Configuration invalid")
        self.assertEqual(str(context.exception), "Configuration invalid")
        self.assertIsInstance(context.exception, AuthorizationError)

    def test_118_authorization_error(self):
        """Test AuthorizationError exception"""
        with self.assertRaises(AuthorizationError) as context:
            raise AuthorizationError("Test error message")
        self.assertEqual(str(context.exception), "Test error message")

    def test_119_authorization_not_found_error(self):
        """Test AuthorizationNotFoundError exception"""
        with self.assertRaises(AuthorizationNotFoundError) as context:
            raise AuthorizationNotFoundError("Authorization not found")
        self.assertEqual(str(context.exception), "Authorization not found")
        self.assertIsInstance(context.exception, AuthorizationError)

    def test_120_authorization_expired_error(self):
        """Test AuthorizationExpiredError exception"""
        with self.assertRaises(AuthorizationExpiredError) as context:
            raise AuthorizationExpiredError("Authorization expired")
        self.assertEqual(str(context.exception), "Authorization expired")
        self.assertIsInstance(context.exception, AuthorizationError)

    def test_121_configuration_error(self):
        """Test ConfigurationError exception"""
        with self.assertRaises(ConfigurationError) as context:
            raise ConfigurationError("Configuration invalid")
        self.assertEqual(str(context.exception), "Configuration invalid")
        self.assertIsInstance(context.exception, AuthorizationError)


class TestAuthorizationRepositoryLogging(unittest.TestCase):
    """Test that AuthorizationRepository logs errors/criticals on exception paths."""

    def setUp(self):
        from acme2certifier.acme_srv.authorization import AuthorizationRepository

        self.mock_dbstore = Mock()
        self.mock_logger = Mock()
        self.repo = AuthorizationRepository(self.mock_dbstore, self.mock_logger)

    def test_122_authorization_expiry_logs_error(self):
        self.mock_dbstore.authorization_update.side_effect = Exception("fail")
        with self.assertRaises(Exception):
            self.repo.update_authorization_expiry("authz", "token", 123)
        self.mock_logger.error.assert_called()
        args = self.mock_logger.error.call_args[0]
        self.assertIn("Database error during authorization update", args[0])

    def test_123_authorization_as_valid_logs_critical(self):
        self.mock_dbstore.authorization_update.side_effect = Exception("fail")
        with self.assertRaises(Exception):
            self.repo.mark_authorization_as_valid("authz")
        self.mock_logger.critical.assert_called()
        args = self.mock_logger.critical.call_args[0]
        self.assertIn("Database error: failed to update authorization", args[0])

    def test_124_order_as_ready_logs_critical(self):
        self.mock_dbstore.order_update.side_effect = Exception("fail")
        with self.assertRaises(Exception):
            self.repo.mark_order_as_ready("order1")
        self.mock_logger.critical.assert_called()
        args = self.mock_logger.critical.call_args[0]
        self.assertIn("Database error: failed to update order", args[0])

    def test_125_authorization_as_expired_logs_critical(self):
        self.mock_dbstore.authorization_update.side_effect = Exception("fail")
        with self.assertRaises(Exception):
            self.repo.mark_authorization_as_expired("authz")
        self.mock_logger.critical.assert_called()
        args = self.mock_logger.critical.call_args[0]
        self.assertIn("Database error: failed to update authorization", args[0])


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