feat: CRMデータ取得処理のテスト追加。リトライのテストが途中

This commit is contained in:
shimoda.m@nds-tyo.co.jp 2022-08-17 11:59:43 +09:00
parent 03b4089fd3
commit ff6b41b18d

View File

@ -0,0 +1,308 @@
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] のレコード取得に失敗しました エラー内容:[生成エラー]'
def test_raise_fetch_sf_count_connection_timeout_with_retry_success(self, monkeypatch, caplog):
"""
Cases:
データ件数取得処理で接続タイムアウト例外が発生した場合リトライした結果復旧し正常終了すること
Arranges:
- データ件数取得処理の最大リトライ試行回数を3に設定する
- データ件数取得処理の初回に接続タイムアウト例外が発生するようにする
Expects:
- 正常終了する
- データ件数取得に失敗した胸のエラーが出力されない
"""
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 3)
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('src.fetch_crm_data_process.CRM_AUTH_TIMEOUT', 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]
mock_counter_inst.increment.side_effect = [2, 3, 4]
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 = [ConnectTimeout('接続タイムアウト'), 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
retry_log_message = 'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします[1] エラー内容:[接続タイムアウト]'
assert generate_log_message_tuple(
log_level=logging.WARNING,
log_message=retry_log_message) in caplog.record_tuples
called_log_counts = len([log for log in caplog.messages if log == retry_log_message])
assert called_log_counts == 1
assert generate_log_message_tuple(log_message='I-FETCH-06 [Account] のCRMからのデータ取得処理を終了します') in caplog.record_tuples
def test_raise_fetch_sf_count_connection_timeout_with_retry_fail(self, monkeypatch, caplog):
"""
Cases:
データ件数取得処理で接続タイムアウト例外が発生した場合リトライした結果復旧せず異常終了すること
Arranges:
- データ件数取得処理の最大リトライ試行回数を3に設定する
- データ件数取得処理の1回目2回目3回目で接続タイムアウト例外が発生するようにする
Expects:
- 正常終了する
- データ件数取得に失敗した胸のエラーが出力されない
"""
monkeypatch.setattr('src.fetch_crm_data_process.CRM_GET_RECORD_COUNT_MAX_RETRY_ATTEMPT', 3)
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('src.fetch_crm_data_process.CRM_AUTH_TIMEOUT', 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]
mock_counter_inst.increment.side_effect = [2, 3, 4]
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 = [ConnectTimeout('接続タイムアウト'), ConnectTimeout('接続タイムアウト'), ConnectTimeout('接続タイムアウト')]
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
# 取得は3回行われる
assert mock_counter_inst.describe.call_count == 3
# 足し込みは2回のみ
assert mock_counter_inst.increment.call_count == 2
retry_log_message = 'W-FETCH-01 CRMの接続処理がタイムアウトしため、リトライします[1] エラー内容:[接続タイムアウト]'
assert generate_log_message_tuple(
log_level=logging.WARNING,
log_message=retry_log_message) in caplog.record_tuples
called_log_counts = len([log for log in caplog.messages if log == retry_log_message])
assert called_log_counts == 2
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]