import logging
from collections import OrderedDict
from unittest.mock import patch

import pytest
from requests.exceptions import ConnectTimeout, ReadTimeout
from src.config.objects import LastFetchDatetime, TargetObject
from src.error.exceptions import SalesforceAPIException
from src.fetch_crm_data_process import fetch_crm_data_process
from src.system_var.constants import FETCH_JP_NAME
from src.util.execute_datetime import ExecuteDateTime

from .test_utils.log_message import generate_log_message_tuple

common_target_object_dict = {
    'object_name': 'Account',
    'columns': [
        'Id',
        'AccountNumber',
        'LastModifiedDate',
        'LastModifiedById',
        'SystemModstamp',
        'IsDeleted'
    ]
}

common_last_fetch_datetime_dict = {
    'last_fetch_datetime_from': '2000-01-01T00:00:00.000Z',
    'last_fetch_datetime_to': '2010-01-01T00:00:00.000Z',
}

common_execute_datetime = ExecuteDateTime()
common_target_object = TargetObject(common_target_object_dict, common_execute_datetime)
common_last_fetch_datetime = LastFetchDatetime(common_last_fetch_datetime_dict, common_execute_datetime)

common_expect = [
    OrderedDict([
        ('attributes', OrderedDict([('type', 'Account'), ('url', '/services/data/v1.0/sobjects/Account/TEST001')])),
        ('Id', 'TEST001'),
        ('AccountNumber', 'test001'),
        ('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
        ('LastModifiedById', 1.234567E+6),
        ('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
        ('IsDeleted', False)
    ]),
    OrderedDict([
        ('attributes', OrderedDict([('type', 'Account'), ('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
        ('Id', 'TEST002'),
        ('AccountNumber', 'test002'),
        ('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
        ('LastModifiedById', 1.234567E+6),
        ('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
        ('IsDeleted', False)
    ]),
    OrderedDict([
        ('attributes', OrderedDict([('type', 'Account'), ('url', '/services/data/v1.0/sobjects/Account/TEST002')])),
        ('Id', 'TEST002'),
        ('AccountNumber', 'test002'),
        ('LastModifiedDate', '2022-06-01T00:00:00.000+0000'),
        ('LastModifiedById', 1.234567E+6),
        ('SystemModstamp', '2022-06-01T00:00:00.000+0000'),
        ('IsDeleted', False)
    ]),
]


class TestFetchCrmDataProcess:

    def test_run_process_success(self, monkeypatch, caplog):
        """
        Cases:
            CRMデータ取得処理が正常終了し、期待通りの結果が返ること
        Arranges:
            - SalesforceApiClientクラスをモック化し、固定の値を返すようにする
        Expects:
            - CRMから取得した取得オブジェクトのリストが返却される
            - CRMデータ取得処理の仕様に沿った正常系ログが出力されること(デバッグログは除く)
        """
        # Arrange

        # モック化
        with patch('src.fetch_crm_data_process.SalesforceApiClient') as mock:
            instance = mock.return_value
            instance.fetch_sf_count.return_value = len(common_expect)
            instance.fetch_sf_data.return_value = common_expect
            # Act
            actual_crm_data_response = fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        # 返り値の期待値チェック
        assert isinstance(actual_crm_data_response, list)
        assert isinstance(actual_crm_data_response[0], OrderedDict)
        assert actual_crm_data_response == common_expect

        # ログの確認
        assert generate_log_message_tuple(log_message='I-FETCH-01 [Account] のCRMからのデータ取得処理を開始します') in caplog.record_tuples
        assert generate_log_message_tuple(log_message='I-FETCH-02 [Account] の件数取得を開始します') in caplog.record_tuples
        assert generate_log_message_tuple(log_message='I-FETCH-03 [Account] の件数：[3]') in caplog.record_tuples
        assert generate_log_message_tuple(log_message='I-FETCH-04 [Account] のレコード取得を開始します') in caplog.record_tuples
        assert generate_log_message_tuple(log_message='I-FETCH-05 [Account] のレコード取得が成功しました') in caplog.record_tuples
        assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples

    def test_call_depended_modules(self):
        """
        Cases:
            CRMデータ取得処理内で依存しているモジュールが正しく呼ばれていること
        Arranges:
            - CRMデータ取得処理の依存モジュールをモック化する
        Expects:
            - 依存しているモジュールが正しく呼ばれている
        """

        # Arrange
        with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
                patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_counter_inst = mock_counter.return_value
            mock_counter_inst.describe.return_value = 1
            mock_counter_inst.increment.return_value = 1
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.return_value = ''
            mock_builder_inst.create_fetch_soql.return_value = ''
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.return_value = 1
            mock_client_inst.fetch_sf_data.return_value = common_expect

            # Act
            fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        assert mock_counter.call_count == 2, 'リトライカウント用クラスのインスタンスが生成されていること'
        assert mock_counter_inst.describe.called is False, 'リトライ用のカウントが取得されていないこと'
        assert mock_counter_inst.increment.called is False, 'リトライ用のカウントが足されていないこと'
        assert mock_soql_builder.call_count == 1, 'SOQL生成クラスのインスタンスが生成されていること'
        assert mock_builder_inst.create_count_soql.called is True, '件数取得SOQLが生成されていること'
        assert mock_builder_inst.create_fetch_soql.called is True, 'データ取得SOQLが生成されていること'
        assert mock_api_client.call_count == 2, 'Salesforce APIクライアントのインスタンスが生成されていること'
        assert mock_client_inst.fetch_sf_count.called is True, '件数の取得が呼ばれていること'
        assert mock_client_inst.fetch_sf_data.called is True, 'レコードの取得が呼ばれていること'

    def test_raise_create_count_soql(self, monkeypatch, caplog):
        """
        Cases:
            件数取得用SOQLが生成できない場合、エラーが発生すること
        Arranges:
            - 件数取得用SOQL生成処理で例外が発生するようにする
        Expects:
            - 例外が発生する
            - データ件数取得に失敗した旨のエラーが出力される
        """

        # Arrange
        with patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.side_effect = Exception('生成エラー')
            mock_builder_inst.create_fetch_soql.return_value = ''
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.return_value = 1
            mock_client_inst.fetch_sf_data.return_value = common_expect

            # Act
            with pytest.raises(SalesforceAPIException) as e:
                fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        assert e.value.error_id == 'E-FETCH-01'
        assert e.value.func_name == FETCH_JP_NAME
        assert e.value.args[0] == f'[Account] の件数取得に失敗しました エラー内容：[生成エラー]'

    def test_raise_create_fetch_soql(self, monkeypatch, caplog):
        """
        Cases:
            データ取得用SOQLが生成できない場合、エラーが発生すること
        Arranges:
            - データ取得用SOQL生成処理で例外が発生するようにする
        Expects:
            - 例外が発生する
            - データ取得に失敗した旨のエラーが出力される
        """

        # Arrange
        with patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.return_value = ''
            mock_builder_inst.create_fetch_soql.side_effect = Exception('生成エラー')
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.return_value = 1
            mock_client_inst.fetch_sf_data.return_value = common_expect

            # Act
            with pytest.raises(SalesforceAPIException) as e:
                fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        assert e.value.error_id == 'E-FETCH-02'
        assert e.value.func_name == FETCH_JP_NAME
        assert e.value.args[0] == f'[Account] のレコード取得に失敗しました エラー内容：[生成エラー]'

    @pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
        ('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします：[1] エラー内容：[接続タイムアウト]'),
        ('CRM_GET_RECORD_COUNT_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-02 [Account] の件数取得処理がタイムアウトしたため、リトライします：[1] エラー内容：[読み取りタイムアウト]'),
        ('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-03 [Account] の件数取得に失敗したため、リトライします  エラー内容：[予期せぬ例外]'),
    ], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
    def test_raise_fetch_sf_count_with_retry_success(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
        """
        Cases:
            1. データ件数取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
            2. データ件数取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
            3. データ件数取得処理で予期せぬ例外が発生した場合、リトライした結果復旧し、正常終了すること
        Arranges:
            - データ件数取得処理の最大リトライ試行回数を4に設定する
            - timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
            - データ件数取得処理の初回に接続タイムアウト例外が発生するようにする
        Expects:
            - 正常終了する
            - データ件数取得に失敗した旨のエラーが出力されない
        """

        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 4)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_INTERVAL', 1)
        monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
        # Arrange
        with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
                patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_counter_inst = mock_counter.return_value
            mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
            mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.return_value = ''
            mock_builder_inst.create_fetch_soql.return_value = ''
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.side_effect = [exception, len(common_expect)]
            mock_client_inst.fetch_sf_data.return_value = common_expect
            # Act
            fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        assert mock_counter_inst.describe.call_count == 1
        assert mock_counter_inst.increment.call_count == 1

        assert generate_log_message_tuple(
            log_level=logging.WARNING,
            log_message=expect_message) in caplog.record_tuples
        called_log_counts = len([log for log in caplog.messages if log == expect_message])
        assert called_log_counts == 1
        assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples

    @pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
        ('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします：[1] エラー内容：[接続タイムアウト]'),
        ('CRM_GET_RECORD_COUNT_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-02 [Account] の件数取得処理がタイムアウトしたため、リトライします：[1] エラー内容：[読み取りタイムアウト]'),
        ('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-03 [Account] の件数取得に失敗したため、リトライします  エラー内容：[予期せぬ例外]'),
    ], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
    def test_raise_fetch_sf_count_with_retry_fail(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
        """
        Cases:
            1. データ件数取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
            2. データ件数取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
            3. データ件数取得処理で予期せぬ例外が発生した場合、リトライした結果復旧せず、異常終了すること
        Arranges:
            - データ件数取得処理の最大リトライ試行回数を4に設定する
            - timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
            - データ件数取得処理の1回目、2回目、3回目、4回目で接続タイムアウト例外が発生するようにする
        Expects:
            - 異常終了する
            - データ件数取得に失敗した旨のエラーが出力される
        """

        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 4)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_INTERVAL', 1)
        monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
        # Arrange
        with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
                patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_counter_inst = mock_counter.return_value
            mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
            mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.return_value = ''
            mock_builder_inst.create_fetch_soql.return_value = ''
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.side_effect = [exception, exception, exception, exception]
            mock_client_inst.fetch_sf_data.return_value = common_expect
            # Act
            with pytest.raises(SalesforceAPIException) as e:
                fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        # 取得は4回行われる
        assert mock_counter_inst.describe.call_count == 4
        # 足し込みは3回のみ
        assert mock_counter_inst.increment.call_count == 3

        assert generate_log_message_tuple(
            log_level=logging.WARNING,
            log_message=expect_message) in caplog.record_tuples
        called_log_counts = len([log for log in caplog.messages if log == expect_message])
        assert called_log_counts == 3
        assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') not in caplog.record_tuples

        assert e.value.error_id == 'E-FETCH-01'
        assert e.value.func_name == FETCH_JP_NAME
        # リトライ例外のオブジェクトIDが違うため、in句で比較
        assert f'[Account] の件数取得に失敗しました エラー内容：[RetryError' in e.value.args[0]

    @pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
        ('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-04 CRMの接続処理がタイムアウトしため、リトライします：[1] エラー内容：[接続タイムアウト]'),
        ('CRM_FETCH_RECORD_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-05 [Account] のレコード取得処理がタイムアウトしたため、リトライします：[1] エラー内容：[読み取りタイムアウト]'),
        ('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-06 [Account] のレコード取得に失敗したため、リトライします  エラー内容：[予期せぬ例外]'),
    ], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
    def test_raise_fetch_sf_data_with_retry_success(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
        """
        Cases:
            1. レコード取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
            2. レコード取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧し、正常終了すること
            3. レコード取得処理で予期せぬ例外が発生した場合、リトライした結果復旧し、正常終了すること
        Arranges:
            - レコード取得処理の最大リトライ試行回数を4に設定する
            - timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
            - レコード取得処理の初回に接続タイムアウト例外が発生するようにする
        Expects:
            - 正常終了する
            - データレコード取得に失敗した旨のエラーが出力されない
        """

        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 4)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MAX_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_MIN_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_RETRY_INTERVAL', 1)
        monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
        # Arrange
        with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
                patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_counter_inst = mock_counter.return_value
            mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
            mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.return_value = ''
            mock_builder_inst.create_fetch_soql.return_value = ''
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.return_value = 1
            mock_client_inst.fetch_sf_data.side_effect = [exception, common_expect]
            # Act
            fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        assert mock_counter_inst.describe.call_count == 1
        assert mock_counter_inst.increment.call_count == 1

        assert generate_log_message_tuple(
            log_level=logging.WARNING,
            log_message=expect_message) in caplog.record_tuples
        called_log_counts = len([log for log in caplog.messages if log == expect_message])
        assert called_log_counts == 1
        assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples

    @pytest.mark.parametrize('timeout_env_name, exception, expect_message', [
        ('CRM_AUTH_TIMEOUT', ConnectTimeout('接続タイムアウト'), 'W-FETCH-04 CRMの接続処理がタイムアウトしため、リトライします：[1] エラー内容：[接続タイムアウト]'),
        ('CRM_FETCH_RECORD_TIMEOUT', ReadTimeout('読み取りタイムアウト'), 'W-FETCH-05 [Account] のレコード取得処理がタイムアウトしたため、リトライします：[1] エラー内容：[読み取りタイムアウト]'),
        ('CRM_AUTH_TIMEOUT', Exception('予期せぬ例外'), 'W-FETCH-06 [Account] のレコード取得に失敗したため、リトライします  エラー内容：[予期せぬ例外]'),
    ], ids=['connection_timeout', 'read_timeout', 'unexpected_exception'])
    def test_raise_fetch_sf_data_with_retry_fail(self, monkeypatch, caplog, timeout_env_name, exception, expect_message):
        """
        Cases:
            1. レコード取得処理で接続タイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
            2. レコード取得処理で読み取りタイムアウト例外が発生した場合、リトライした結果復旧せず、異常終了すること
            3. レコード取得処理で予期せぬ例外が発生した場合、リトライした結果復旧せず、異常終了すること
        Arranges:
            - レコード取得処理の最大リトライ試行回数を4に設定する
            - timeout_env_nameに指定されたリトライタイムアウト時間の秒数を1に設定する
            - レコード取得処理の1回目、2回目、3回目、4回目で接続タイムアウト例外が発生するようにする
        Expects:
            - 異常終了する
            - データレコード取得に失敗した旨のエラーが出力される
        """

        monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_MAX_RETRY_ATTEMPT', 4)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_RETRY_MAX_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_RETRY_MIN_INTERVAL', 1)
        monkeypatch.setattr('src.fetch_crm_data_process.CRM_FETCH_RECORD_RETRY_INTERVAL', 1)
        monkeypatch.setattr(f'src.fetch_crm_data_process.{timeout_env_name}', 1)
        # Arrange
        with patch('src.fetch_crm_data_process.CounterObject', ) as mock_counter, \
                patch('src.fetch_crm_data_process.SOQLBuilder') as mock_soql_builder, \
                patch('src.fetch_crm_data_process.SalesforceApiClient') as mock_api_client:
            # モック化
            mock_counter_inst = mock_counter.return_value
            mock_counter_inst.describe.side_effect = [1, 2, 3, 4]
            mock_counter_inst.increment.side_effect = [2, 3, 4, 5]
            mock_builder_inst = mock_soql_builder.return_value
            mock_builder_inst.create_count_soql.return_value = ''
            mock_builder_inst.create_fetch_soql.return_value = ''
            mock_client_inst = mock_api_client.return_value
            mock_client_inst.fetch_sf_count.return_value = 1
            mock_client_inst.fetch_sf_data.side_effect = [exception, exception, exception, exception]
            # Act
            with pytest.raises(SalesforceAPIException) as e:
                fetch_crm_data_process(common_target_object, common_last_fetch_datetime)

        # Assert

        # 取得は4回行われる
        assert mock_counter_inst.describe.call_count == 4
        # 足し込みは3回のみ
        assert mock_counter_inst.increment.call_count == 3

        assert generate_log_message_tuple(
            log_level=logging.WARNING,
            log_message=expect_message) in caplog.record_tuples
        called_log_counts = len([log for log in caplog.messages if log == expect_message])
        assert called_log_counts == 3
        assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') not in caplog.record_tuples

        assert e.value.error_id == 'E-FETCH-02'
        assert e.value.func_name == FETCH_JP_NAME
        # リトライ例外のオブジェクトIDが違うため、in句で比較
        assert f'[Account] のレコード取得に失敗しました エラー内容：[RetryError' in e.value.args[0]
