diff --git a/ecs/crm-datafetch/README.md b/ecs/crm-datafetch/README.md index 243e33e5..f2928706 100644 --- a/ecs/crm-datafetch/README.md +++ b/ecs/crm-datafetch/README.md @@ -74,7 +74,7 @@ ```text . -├── Dockerfile -- Dokcerイメージを作成するためのファイル +├── Dockerfile -- Dockerイメージを作成するためのファイル ├── Pipfile -- Pipenv(Pythonの仮想環境管理モジュール)で、依存関係を管理するためのファイル ├── Pipfile.lock -- Pipenvでインストールされた依存関係のバージョン固定ファイル ├── README.md -- README @@ -105,7 +105,8 @@ │ └── logger.py -- ログ管理クラス │ └── tests/ -- テストコード置き場 - ├── aws -- AWS操作モジュールのテスト + ├── test_utils/ -- テストコードで共通的に使用できる関数群 + ├── aws/ -- AWS操作モジュールのテスト ├── ... -- src配下のモジュール構成と同じ階層にテストコードを追加していく ├── conftest.py -- pytestのフィクスチャやフックを管理するファイル └── docstring_parser.py -- pytest-htmlのレポート出力用のヘルパー diff --git a/ecs/crm-datafetch/main.py b/ecs/crm-datafetch/main.py index dcae9825..525e2367 100644 --- a/ecs/crm-datafetch/main.py +++ b/ecs/crm-datafetch/main.py @@ -2,4 +2,7 @@ from src.controller import controller """CRMデータ取得処理のエントリーポイント""" if __name__ == '__main__': - controller() + try: + exit(controller()) + except Exception: + exit(0) diff --git a/ecs/crm-datafetch/src/config/objects.py b/ecs/crm-datafetch/src/config/objects.py index 41ca753b..70beb2eb 100644 --- a/ecs/crm-datafetch/src/config/objects.py +++ b/ecs/crm-datafetch/src/config/objects.py @@ -1,19 +1,13 @@ -from src.system_var.constants import (COLUMNS_KEY, COLUMNS_TYPE, - DATE_PATTERN_YYYYMMDDTHHMMSSTZ, - DATETIME_COLUMN_DEFAULT_VALUE, - DATETIME_COLUMN_KEY, - DATETIME_COLUMN_TYPE, IS_SKIP_KEY, - IS_SKIP_TYPE, - IS_UPDATE_LAST_FETCH_DATETIME_KEY, - IS_UPDATE_LAST_FETCH_DATETIME_TYPE, - LAST_FETCH_DATETIME_FILE_NAME_KEY, - LAST_FETCH_DATETIME_FILE_NAME_TYPE, - LAST_FETCH_DATETIME_FROM_KEY, - LAST_FETCH_DATETIME_TO_KEY, - OBJECT_NAME_KEY, OBJECT_NAME_TYPE, - OBJECTS_KEY, OBJECTS_TYPE, - UPLOAD_FILE_NAME_KEY, - UPLOAD_FILE_NAME_TYPE) +from src.system_var.constants import ( + COLUMNS_KEY, COLUMNS_TYPE, DATE_PATTERN_YYYYMMDDTHHMMSSTZ, + DATETIME_COLUMN_DEFAULT_VALUE, DATETIME_COLUMN_KEY, DATETIME_COLUMN_TYPE, + DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ, IS_SKIP_KEY, IS_SKIP_TYPE, + IS_UPDATE_LAST_FETCH_DATETIME_KEY, IS_UPDATE_LAST_FETCH_DATETIME_TYPE, + LAST_FETCH_DATETIME_FILE_NAME_KEY, LAST_FETCH_DATETIME_FILE_NAME_TYPE, + LAST_FETCH_DATETIME_FROM_KEY, LAST_FETCH_DATETIME_FROM_TYPE, + LAST_FETCH_DATETIME_TO_KEY, LAST_FETCH_DATETIME_TO_TYPE, OBJECT_NAME_KEY, + OBJECT_NAME_TYPE, OBJECTS_KEY, OBJECTS_TYPE, UPLOAD_FILE_NAME_KEY, + UPLOAD_FILE_NAME_TYPE) from src.util.dict_checker import DictChecker from src.util.execute_datetime import ExecuteDateTime @@ -22,7 +16,7 @@ class FetchTargetObjects(): def __init__(self, object_info_file_dict) -> None: self.__objects = object_info_file_dict self.__dict_checker = DictChecker(self.__objects) - self.validate() + self.__validate() self.__i = 0 def __iter__(self): @@ -35,7 +29,7 @@ class FetchTargetObjects(): self.__i += 1 return value - def validate(self) -> None: + def __validate(self) -> None: self.__dict_checker.assert_key_exist(OBJECTS_KEY) self.__dict_checker.assert_data_type(OBJECTS_KEY, OBJECTS_TYPE) @@ -58,6 +52,7 @@ class TargetObject(): self.__dict_checker.assert_data_type(OBJECT_NAME_KEY, OBJECT_NAME_TYPE) self.__dict_checker.assert_key_exist(COLUMNS_KEY) self.__dict_checker.assert_data_type(COLUMNS_KEY, COLUMNS_TYPE) + self.__dict_checker.assert_list_empty(COLUMNS_KEY) return @@ -95,7 +90,7 @@ class TargetObject(): def is_update_last_fetch_datetime(self) -> bool: if self.__dict_checker.check_key_exist(IS_UPDATE_LAST_FETCH_DATETIME_KEY): return self.__object_info[IS_UPDATE_LAST_FETCH_DATETIME_KEY] - return False + return True @property def last_fetch_datetime_file_name(self) -> str: @@ -107,7 +102,7 @@ class TargetObject(): def upload_file_name(self) -> str: if self.__dict_checker.check_key_exist(UPLOAD_FILE_NAME_KEY): return self.__object_info[UPLOAD_FILE_NAME_KEY].format(execute_datetime=self.__execute_datetime.format_date()) - return f'{self.__object_info[OBJECT_NAME_KEY]}_{self.__execute_datetime.format_date()}' + return f'CRM_{self.__object_info[OBJECT_NAME_KEY]}_{self.__execute_datetime.format_date()}' @property def datetime_column(self) -> str: @@ -122,10 +117,12 @@ class LastFetchDatetime(): self.__validate() def __validate(self) -> None: - if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_FROM_KEY): - self.__dict_checker.assert_match_pattern(LAST_FETCH_DATETIME_FROM_KEY, DATE_PATTERN_YYYYMMDDTHHMMSSTZ) + self.__dict_checker.assert_key_exist(LAST_FETCH_DATETIME_FROM_KEY) + self.__dict_checker.assert_data_type(LAST_FETCH_DATETIME_FROM_KEY, LAST_FETCH_DATETIME_FROM_TYPE) + self.__dict_checker.assert_match_pattern(LAST_FETCH_DATETIME_FROM_KEY, DATE_PATTERN_YYYYMMDDTHHMMSSTZ,DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ) if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_TO_KEY): - self.__dict_checker.assert_match_pattern(LAST_FETCH_DATETIME_TO_KEY, DATE_PATTERN_YYYYMMDDTHHMMSSTZ) + self.__dict_checker.assert_data_type(LAST_FETCH_DATETIME_TO_KEY, LAST_FETCH_DATETIME_TO_TYPE) + self.__dict_checker.assert_match_pattern(LAST_FETCH_DATETIME_TO_KEY, DATE_PATTERN_YYYYMMDDTHHMMSSTZ,DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ) return @property @@ -136,4 +133,4 @@ class LastFetchDatetime(): def last_fetch_datetime_to(self) -> str: if self.__dict_checker.check_key_exist(LAST_FETCH_DATETIME_TO_KEY): return self.__last_fetch_datetime_file_dict[LAST_FETCH_DATETIME_TO_KEY] - return self.__execute_datetime + return str(self.__execute_datetime) diff --git a/ecs/crm-datafetch/src/controller.py b/ecs/crm-datafetch/src/controller.py index 3d0537b4..f9c87960 100644 --- a/ecs/crm-datafetch/src/controller.py +++ b/ecs/crm-datafetch/src/controller.py @@ -33,37 +33,40 @@ def controller() -> None: # ③ object_infoのobjectsキーの値の件数分ループする logger.info('I-CTRL-03 取得対象オブジェクトのループ処理開始') - process_result = fetch_crm_data(fetch_target_objects, execute_datetime, process_result) + process_result = _fetch_crm_data(fetch_target_objects, execute_datetime, process_result) # ④ すべてのオブジェクトの処理が完了したことと、オブジェクト毎の処理結果をログに出力する logger.info(f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{process_result}]') + # 最終結果が0件(1件も処理されていない)の場合、ログ出力して処理を終了する + if len(process_result.keys()) == 0: + logger.info('I-CTRL-21 処理対象のデータが存在しませんでした') + return 0 + # ⑤ 取得処理実施結果アップロード処理を呼び出す logger.info('I-CTRL-18 CRM_取得処理実施結果ファイルアップロード処理開始') upload_result_data_process(process_result, execute_datetime) # ⑥ 最終結果をチェックし、チェック結果をログに出力 - if not all([v == 'success' for v in process_result.values()]): - logger.error('E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') - else: - logger.info('I-CTRL-19 すべてのデータの取得に成功しました') + _check_process_result(process_result) - # ⑦ CRMデータ取得処理終了ログを出力する - logger.info('I-CTRL-20 CRMデータ取得処理を終了します') - - return exit(0) + return 0 except MeDaCaCRMDataFetchException as e: logger.error(f'E-ERR-01 [{e.func_name}]でエラーが発生したため、処理を終了します') logger.exception(f'{e.error_id} {e}') - return exit(0) + raise e except Exception as e: - logger.exception('E-ERR-02 予期せぬエラーが発生したため、処理を終了します', e) - return exit(0) + logger.exception(f'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [{e}]') + raise e + + finally: + # ⑦ CRMデータ取得処理終了ログを出力する + logger.info('I-CTRL-20 CRMデータ取得処理を終了します') -def fetch_crm_data(fetch_target_objects: FetchTargetObjects, execute_datetime: ExecuteDateTime, process_result: dict): +def _fetch_crm_data(fetch_target_objects: FetchTargetObjects, execute_datetime: ExecuteDateTime, process_result: dict): """取得対象オブジェクト情報をループし、1オブジェクトごとのデータを取得する Args: @@ -79,7 +82,7 @@ def fetch_crm_data(fetch_target_objects: FetchTargetObjects, execute_datetime: E try: process_result[object_info.get(OBJECT_NAME_KEY)] = 'fail' - fetch_crm_data_per_object(object_info, execute_datetime) + _fetch_crm_data_per_object(object_info, execute_datetime) process_result[object_info.get(OBJECT_NAME_KEY)] = 'success' @@ -91,13 +94,13 @@ def fetch_crm_data(fetch_target_objects: FetchTargetObjects, execute_datetime: E except Exception as e: logger.info( - f'I-ERR-04 [{object_info.get(OBJECT_NAME_KEY)}] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します', e, exc_info=True) + f'I-ERR-04 [{object_info.get(OBJECT_NAME_KEY)}] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [{e}]', exc_info=True) continue return process_result -def fetch_crm_data_per_object(object_info: dict, execute_datetime: ExecuteDateTime) -> None: +def _fetch_crm_data_per_object(object_info: dict, execute_datetime: ExecuteDateTime) -> None: """オブジェクトごとにCRMのデータを取得し、取込フォルダにアップロードする Args: @@ -136,6 +139,12 @@ def fetch_crm_data_per_object(object_info: dict, execute_datetime: ExecuteDateTi crm_data_response = fetch_crm_data_process(target_object, last_fetch_datetime) + # 取得件数が0件の場合、次のオブジェクトの処理に移行する + if len(crm_data_response) == 0: + logger.info( + f'I-CTRL-22 [{target_object_name}]のレコード件数が0件のため、ファイルアップロードをスキップします') + return + # 7. 出力ファイル名をログ出力する logger.info( f'I-CTRL-10 [{target_object_name}] の出力ファイル名は [{target_object.upload_file_name}] となります') @@ -174,3 +183,18 @@ def fetch_crm_data_per_object(object_info: dict, execute_datetime: ExecuteDateTi logger.info(f'I-CTRL-16 [{target_object_name}] 処理正常終了') return + + +def _check_process_result(process_result: dict) -> None: + """取得処理結果がすべて成功か、一部失敗しているかを判定し、ログ出力する + + Args: + process_result (dict): 取得処理結果辞書オブジェクト + """ + if not all([v == 'success' for v in process_result.values()]): + logger.error('E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') + return + + logger.info('I-CTRL-19 すべてのデータの取得に成功しました') + + return diff --git a/ecs/crm-datafetch/src/system_var/constants.py b/ecs/crm-datafetch/src/system_var/constants.py index bf65010c..9a8f6cd2 100644 --- a/ecs/crm-datafetch/src/system_var/constants.py +++ b/ecs/crm-datafetch/src/system_var/constants.py @@ -50,8 +50,10 @@ S3_CHAR_CODE = 'utf-8' # 正規表現チェック EXCLUDE_SYMBOL = ['#', '/'] DATE_PATTERN_YYYYMMDDTHHMMSSTZ = r'[12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.000Z' +DATE_PATTERN_EXPECTED_YYYYMMDDTHHMMSSTZ = 'YYYY-MM-DDTHH:MM:SS.000Z' DATE_PATTERN_YYYYMMDDHHMMSSFFF_UTC = r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.000\+0000' + # logger LOG_FORMAT = '[%(levelname)s]\t%(asctime)s\t%(message)s\n' LOG_DATE_FORMAT = '%Y-%m-%d %H:%M:%S' @@ -93,4 +95,6 @@ DATETIME_COLUMN_KEY = 'datetime_column' DATETIME_COLUMN_TYPE = str DATETIME_COLUMN_DEFAULT_VALUE = 'SystemModstamp' LAST_FETCH_DATETIME_TO_KEY = 'last_fetch_datetime_to' +LAST_FETCH_DATETIME_TO_TYPE = str LAST_FETCH_DATETIME_FROM_KEY = 'last_fetch_datetime_from' +LAST_FETCH_DATETIME_FROM_TYPE = str diff --git a/ecs/crm-datafetch/src/util/counter_object.py b/ecs/crm-datafetch/src/util/counter_object.py index 23da4f82..6cf6b7c2 100644 --- a/ecs/crm-datafetch/src/util/counter_object.py +++ b/ecs/crm-datafetch/src/util/counter_object.py @@ -1,6 +1,6 @@ class CounterObject: def __init__(self, base_num=1) -> None: - self.__counter = base_num + self.__counter = int(base_num) def describe(self) -> int: return self.__counter diff --git a/ecs/crm-datafetch/src/util/dict_checker.py b/ecs/crm-datafetch/src/util/dict_checker.py index 39ebf9f0..87e830e6 100644 --- a/ecs/crm-datafetch/src/util/dict_checker.py +++ b/ecs/crm-datafetch/src/util/dict_checker.py @@ -7,11 +7,15 @@ class DictChecker: def is_empty(self, check_key): """辞書型バリュー空文字チェック""" - return self.__object_dict[check_key] != '' and self.__object_dict[check_key] is not None + return self.__object_dict[check_key] == '' or self.__object_dict[check_key] is None + + def is_list_empty(self, check_key): + """list型データ存在チェック""" + return len(self.__object_dict[check_key]) == 0 def check_key_exist(self, check_key: str) -> bool: """辞書型キー存在チェック""" - return check_key in self.__object_dict and self.is_empty(check_key) + return check_key in self.__object_dict and not self.is_empty(check_key) def check_data_type(self, check_key: str, check_type: type) -> bool: """辞書型バリュー型チェック""" @@ -35,9 +39,13 @@ class DictChecker: return - def assert_match_pattern(self, check_key: str, regex_str: str): + def assert_match_pattern(self, check_key: str, regex_str: str, expected_str: str): """正規表現検査""" if not self.check_match_pattern(regex_str, check_key): - raise Exception(f'「{check_key}」キーの値の正規表現「{regex_str}」チェックに失敗しました') + raise Exception(f'「{check_key}」キーの値の正規表現チェックに失敗しました 「{expected_str}」形式である必要があります') return + + def assert_list_empty(self, check_key: str): + if self.is_list_empty(check_key): + raise Exception(f'「{check_key}」キーのリストの値は必須です') diff --git a/ecs/crm-datafetch/tests/config/test_objects_fetch_target_objects.py b/ecs/crm-datafetch/tests/config/test_objects_fetch_target_objects.py new file mode 100644 index 00000000..f626a6de --- /dev/null +++ b/ecs/crm-datafetch/tests/config/test_objects_fetch_target_objects.py @@ -0,0 +1,467 @@ +import pytest +from src.config.objects import FetchTargetObjects +from src.parser.json_parser import JsonParser + + +class TestFetchTargetObjects(): + + def test_constructor(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キーがあるかまた、キーの型が正しいかをチェック + Arranges: + - オブジェクト情報文字列を準備する + - オブジェクト情報を辞書型にパースする + Expects: + - 例外が発生しないこと + """ + + # Arranges + fetch_objects = '''{ + "objects": [ + { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": false, + "is_update_last_fetch_datetime": true, + "datetime_column": "LastModifiedDate" + }, + { + "object_name": "Contact", + "columns": [ + "Id", + "IsDeleted", + "MasterRecordId", + "AccountId", + "IsPersonAccount", + "LastName", + "FirstName", + "Salutation", + "Name", + "OtherStreet", + "OtherCity", + "OtherState", + "OtherPostalCode", + "OtherCountry", + "OtherLatitude", + "OtherLongitude", + "OtherGeocodeAccuracy", + "OtherAddress", + "MailingStreet", + "MailingCity", + "MailingState", + "MailingPostalCode", + "MailingCountry", + "MailingLatitude", + "MailingLongitude", + "MailingGeocodeAccuracy", + "MailingAddress", + "Phone", + "Fax", + "MobilePhone", + "HomePhone", + "OtherPhone", + "AssistantPhone", + "ReportsToId", + "Email", + "Title", + "Department", + "AssistantName", + "Birthdate", + "Description", + "OwnerId", + "HasOptedOutOfEmail", + "HasOptedOutOfFax", + "DoNotCall", + "CreatedDate", + "CreatedById", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp", + "LastActivityDate", + "LastCURequestDate", + "LastCUUpdateDate", + "MayEdit", + "IsLocked", + "LastViewedDate", + "LastReferencedDate", + "EmailBouncedReason", + "EmailBouncedDate", + "IsEmailBounced", + "PhotoUrl", + "Jigsaw", + "JigsawContactId", + "IndividualId", + "Mobile_ID_vod__c", + "H1Insights__H1_NPI_Value_for_Testing__c", + "H1Insights__H1_Person_ID__c", + "H1Insights__H1_Request_Status__c", + "H1Insights__H1_URL__c", + "H1Insights__NPI_Number__c", + "H1Insights__NPI_Number_for_H1_Insights__c", + "MSJ_Marketing_Cloud_Integration__c" + ], + "is_skip": false, + "is_update_last_fetch_datetime": true + }, + { + "object_name": "Territory2", + "columns": [ + "Id", + "Name", + "Territory2TypeId", + "Territory2ModelId", + "ParentTerritory2Id", + "Description", + "ForecastUserId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp", + "DeveloperName", + "MSJ_Territory_Type__c", + "MSJ_Level__c" + ], + "is_skip": false, + "is_update_last_fetch_datetime": true + } + ] + }''' + + json_parser = JsonParser(fetch_objects) + fetch_objects_dict = json_parser.parse() + + # Act + FetchTargetObjects(fetch_objects_dict) + + # Expects + pass + + def test_raise_constructor_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、必要なキーがない場合、例外が発生すること + Arranges: + - オブジェクト情報文字列を準備する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + fetch_objects_dict = { + "test_objects": [ + { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "datetime_column": "LastModifiedDate" + }, + { + "object_name": "Contact", + "columns": [ + "Id", + "IsDeleted", + "MasterRecordId", + "AccountId", + "IsPersonAccount", + "LastName", + "FirstName", + "Salutation", + "Name", + "OtherStreet", + "OtherCity", + "OtherState", + "OtherPostalCode", + "OtherCountry", + "OtherLatitude", + "OtherLongitude", + "OtherGeocodeAccuracy", + "OtherAddress", + "MailingStreet", + "MailingCity", + "MailingState", + "MailingPostalCode", + "MailingCountry", + "MailingLatitude", + "MailingLongitude", + "MailingGeocodeAccuracy", + "MailingAddress", + "Phone", + "Fax", + "MobilePhone", + "HomePhone", + "OtherPhone", + "AssistantPhone", + "ReportsToId", + "Email", + "Title", + "Department", + "AssistantName", + "Birthdate", + "Description", + "OwnerId", + "HasOptedOutOfEmail", + "HasOptedOutOfFax", + "DoNotCall", + "CreatedDate", + "CreatedById", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp", + "LastActivityDate", + "LastCURequestDate", + "LastCUUpdateDate", + "MayEdit", + "IsLocked", + "LastViewedDate", + "LastReferencedDate", + "EmailBouncedReason", + "EmailBouncedDate", + "IsEmailBounced", + "PhotoUrl", + "Jigsaw", + "JigsawContactId", + "IndividualId", + "Mobile_ID_vod__c", + "H1Insights__H1_NPI_Value_for_Testing__c", + "H1Insights__H1_Person_ID__c", + "H1Insights__H1_Request_Status__c", + "H1Insights__H1_URL__c", + "H1Insights__NPI_Number__c", + "H1Insights__NPI_Number_for_H1_Insights__c", + "MSJ_Marketing_Cloud_Integration__c" + ], + "is_skip": False, + "is_update_last_fetch_datetime": True + }, + { + "object_name": "Territory2", + "columns": [ + "Id", + "Name", + "Territory2TypeId", + "Territory2ModelId", + "ParentTerritory2Id", + "Description", + "ForecastUserId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp", + "DeveloperName", + "MSJ_Territory_Type__c", + "MSJ_Level__c" + ], + "is_skip": False, + "is_update_last_fetch_datetime": True + } + ] + } + + # Act + with pytest.raises(Exception) as e: + FetchTargetObjects(fetch_objects_dict) + + # Expects + assert str(e.value) == '「objects」キーは必須です' + + def test_raise_constructor_no_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキーの値の型が想定と違う場合、例外が発生すること + Arranges: + - オブジェクト情報文字列を準備する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + fetch_objects_dict = { + "objects": "test_value" + } + + # Act + with pytest.raises(Exception) as e: + FetchTargetObjects(fetch_objects_dict) + + # Expects + assert str(e.value) == '「objects」キーの値は「」でなければなりません' + + def test_constructor_iterator(self) -> None: + """ + Cases: + インスタンス生成テスト + 登録されたオブジェクトリストをすべて取り出せること + Arranges: + - オブジェクト情報文字列を準備する + Expects: + - ループが最後まで回ること + """ + + fetch_objects_dict = { + "objects": [ + { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "datetime_column": "LastModifiedDate" + }, + { + "object_name": "Contact", + "columns": [ + "Id", + "IsDeleted", + "MasterRecordId", + "AccountId", + "IsPersonAccount", + "LastName", + "FirstName", + "Salutation", + "Name", + "OtherStreet", + "OtherCity", + "OtherState", + "OtherPostalCode", + "OtherCountry", + "OtherLatitude", + "OtherLongitude", + "OtherGeocodeAccuracy", + "OtherAddress", + "MailingStreet", + "MailingCity", + "MailingState", + "MailingPostalCode", + "MailingCountry", + "MailingLatitude", + "MailingLongitude", + "MailingGeocodeAccuracy", + "MailingAddress", + "Phone", + "Fax", + "MobilePhone", + "HomePhone", + "OtherPhone", + "AssistantPhone", + "ReportsToId", + "Email", + "Title", + "Department", + "AssistantName", + "Birthdate", + "Description", + "OwnerId", + "HasOptedOutOfEmail", + "HasOptedOutOfFax", + "DoNotCall", + "CreatedDate", + "CreatedById", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp", + "LastActivityDate", + "LastCURequestDate", + "LastCUUpdateDate", + "MayEdit", + "IsLocked", + "LastViewedDate", + "LastReferencedDate", + "EmailBouncedReason", + "EmailBouncedDate", + "IsEmailBounced", + "PhotoUrl", + "Jigsaw", + "JigsawContactId", + "IndividualId", + "Mobile_ID_vod__c", + "H1Insights__H1_NPI_Value_for_Testing__c", + "H1Insights__H1_Person_ID__c", + "H1Insights__H1_Request_Status__c", + "H1Insights__H1_URL__c", + "H1Insights__NPI_Number__c", + "H1Insights__NPI_Number_for_H1_Insights__c", + "MSJ_Marketing_Cloud_Integration__c" + ], + "is_skip": False, + "is_update_last_fetch_datetime": True + }, + { + "object_name": "Territory2", + "columns": [ + "Id", + "Name", + "Territory2TypeId", + "Territory2ModelId", + "ParentTerritory2Id", + "Description", + "ForecastUserId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "LastModifiedDate", + "LastModifiedById", + "SystemModstamp", + "DeveloperName", + "MSJ_Territory_Type__c", + "MSJ_Level__c" + ], + "is_skip": False, + "is_update_last_fetch_datetime": True + } + ] + } + + # Act + sut = FetchTargetObjects(fetch_objects_dict) + for i, item in enumerate(sut, 1): + assert item is not None + + # Expects + assert i == 3 diff --git a/ecs/crm-datafetch/tests/config/test_objects_last_fetch_datetime.py b/ecs/crm-datafetch/tests/config/test_objects_last_fetch_datetime.py new file mode 100644 index 00000000..f3712372 --- /dev/null +++ b/ecs/crm-datafetch/tests/config/test_objects_last_fetch_datetime.py @@ -0,0 +1,380 @@ +import pytest +from src.config.objects import LastFetchDatetime +from src.util.execute_datetime import ExecuteDateTime + + +class TestLastFetchDatetime(): + def test_constructor(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、バリデーションチェックを行いチェックが通ることを確認する + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + pass + + def test_raise_constructor_last_fetch_datetime_from_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、必要なキー(last_fetch_datetime_from)がない場合、例外が発生すること + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str(e.value) == '「last_fetch_datetime_from」キーは必須です' + + def test_raise_constructor_last_fetch_datetime_from_no_value(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_from)の値が空文字の場合、例外が発生すること + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "", + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str(e.value) == '「last_fetch_datetime_from」キーは必須です' + + def test_raise_constructor_last_fetch_datetime_from_none_value(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_from)の値がNoneの場合、例外が発生すること + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": None, + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str(e.value) == '「last_fetch_datetime_from」キーは必須です' + + def test_raise_constructor_last_fetch_datetime_from_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_from)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": 1, + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str(e.value) == '「last_fetch_datetime_from」キーの値は「」でなければなりません' + + def test_raise_constructor_last_fetch_datetime_from_other_string(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_from)の値が正規表現と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "aaa", + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str( + e.value) == '「last_fetch_datetime_from」キーの値の正規表現チェックに失敗しました 「YYYY-MM-DDTHH:MM:SS.000Z」形式である必要があります' + + def test_raise_constructor_last_fetch_datetime_to_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キー(last_fetch_datetime_to)がない場合、例外が発生しないこと + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + # Act + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + pass + + def test_constructor_last_fetch_datetime_to_no_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(last_fetch_datetime_to)の値の型が空文字の場合、例外が発生しないこと + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": "" + } + + execute_datetime = ExecuteDateTime() + + # Act + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + None + + def test_constructor_last_fetch_datetime_to_none__value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(last_fetch_datetime_to)の値の型がNoneの場合、例外が発生しないこと + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": None + } + + execute_datetime = ExecuteDateTime() + + # Act + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + pass + + def test_raise_constructor_last_fetch_datetime_to_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_to)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": 1 + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str(e.value) == '「last_fetch_datetime_to」キーの値は「」でなければなりません' + + def test_raise_constructor_last_fetch_datetime_to_other_string(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_to)の値が正規表現と違う場合、例外が発生すること + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": "aaa" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Expects + assert str( + e.value) == '「last_fetch_datetime_to」キーの値の正規表現チェックに失敗しました 「YYYY-MM-DDTHH:MM:SS.000Z」形式である必要があります' + + def test_last_fetch_datetime_from(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + - 前回取得日時インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + sut = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Act + actual = sut.last_fetch_datetime_from + + # Expects + assert actual == '1900-01-01T00:00:00.000Z' + + def test_last_fetch_datetime_to(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + - 前回取得日時インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": "2022-01-01T00:00:00.000Z" + } + + execute_datetime = ExecuteDateTime() + + sut = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Act + actual = sut.last_fetch_datetime_to + + # Expects + assert actual == '2022-01-01T00:00:00.000Z' + + def test_last_fetch_datetime_to_default(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型の前回取得日時データを準備する + - 実行日時インスタンスを生成する + - 前回取得日時インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + last_fetch_datetime_dict = { + "last_fetch_datetime_from": "1900-01-01T00:00:00.000Z", + "last_fetch_datetime_to": "" + } + + execute_datetime = ExecuteDateTime() + + sut = LastFetchDatetime(last_fetch_datetime_dict, execute_datetime) + + # Act + actual = sut.last_fetch_datetime_to + + # Expects + assert actual == str(execute_datetime) diff --git a/ecs/crm-datafetch/tests/config/test_objects_target_object.py b/ecs/crm-datafetch/tests/config/test_objects_target_object.py new file mode 100644 index 00000000..19b89db7 --- /dev/null +++ b/ecs/crm-datafetch/tests/config/test_objects_target_object.py @@ -0,0 +1,1802 @@ +import pytest +from src.config.objects import TargetObject +from src.util.execute_datetime import ExecuteDateTime + + +class TestTargetObject(): + + def test_constructor(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、バリデーションチェックを行いチェックが通ることを確認する + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + # object_name + + def test_raise_constructor_object_name_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、必要なキー(object_name)がない場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「object_name」キーは必須です' + + def test_raise_constructor_object_name_no_value(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(object_name)の値が空文字の場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「object_name」キーは必須です' + + def test_raise_constructor_object_name_none_value(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(object_name)の値がNoneの場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": None, + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「object_name」キーは必須です' + + def test_raise_constructor_object_name_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(object_name)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": 1, + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「object_name」キーの値は「」でなければなりません' + + # columns + + def test_raise_constructor_columns_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、必要なキー(columns)がない場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「columns」キーは必須です' + + def test_raise_constructor_columns_no_value(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(columns)の値が空文字の場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": "", + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「columns」キーは必須です' + + def test_raise_constructor_columns_none_value(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(columns)の値がNoneの場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": None, + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「columns」キーは必須です' + + def test_raise_constructor_columns_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(columns)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": False, + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「columns」キーの値は「」でなければなりません' + + def test_raise_constructor_columns_no_value_list(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(columns)の値がリスト型で空の場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「columns」キーのリストの値は必須です' + + # is_skip + + def test_raise_constructor_is_skip_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キー(is_skip)がない場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_is_skip_no_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(is_skip)の値の型が空文字の場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": "", + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_is_skip_none_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(is_skip)の値の型がNoneの場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": None, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_raise_constructor_is_skip_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(is_skip)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": "False", + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「is_skip」キーの値は「」でなければなりません' + + # is_update_last_fetch_datetime + + def test_raise_constructor_is_update_last_fetch_datetime_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キー(is_update_last_fetch_datetime)がない場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_is_update_last_fetch_datetime_no_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(is_update_last_fetch_datetime)の値の型が空文字の場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": "", + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_is_update_last_fetch_datetime_none_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(is_update_last_fetch_datetime)の値の型がNoneの場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": None, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_raise_constructor_is_update_last_fetch_datetime_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(is_update_last_fetch_datetime)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": "False", + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「is_update_last_fetch_datetime」キーの値は「」でなければなりません' + + # last_fetch_datetime_file_name + + def test_raise_constructor_last_fetch_datetime_file_name_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キー(last_fetch_datetime_file_name)がない場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_last_fetch_datetime_file_name_no_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(last_fetch_datetime_file_name)の値の型が空文字の場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_last_fetch_datetime_file_name_none_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(last_fetch_datetime_file_name)の値の型がNoneの場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": None, + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_raise_constructor_last_fetch_datetime_file_name_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(last_fetch_datetime_file_name)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": 1, + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「last_fetch_datetime_file_name」キーの値は「」でなければなりません' + + # upload_file_name + + def test_raise_constructor_upload_file_name_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キー(upload_file_name)がない場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_upload_file_name_no_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(upload_file_name)の値の型が空文字の場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_upload_file_name_none_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(upload_file_name)の値の型がNoneの場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": None, + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_raise_constructor_upload_file_name_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(upload_file_name)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": 1, + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「upload_file_name」キーの値は「」でなければなりません' + + # datetime_column + + def test_raise_constructor_datetime_column_no_key(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータに対して、キー(datetime_column)がない場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_datetime_column_no_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(datetime_column)の値の型が空文字の場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "" + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_constructor_datetime_column_none_value(self) -> None: + """ + Cases: + 辞書型のデータの対象のキー(datetime_column)の値の型がNoneの場合、例外が発生しないこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生しないこと + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": None + } + + execute_datetime = ExecuteDateTime() + + # Act + TargetObject(object_info, execute_datetime) + + # Expects + pass + + def test_raise_constructor_datetime_column_other_type(self) -> None: + """ + Cases: + インスタンス生成テスト + 辞書型のデータの対象のキー(datetime_column)の値の型が想定と違う場合、例外が発生すること + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": False, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": 1 + } + + execute_datetime = ExecuteDateTime() + + # Act + with pytest.raises(Exception) as e: + TargetObject(object_info, execute_datetime) + + # Expects + assert str(e.value) == '「datetime_column」キーの値は「」でなければなりません' + + # property + + def test_object_name(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.object_name + + # Expects + assert actual == 'AccountShare' + + def test_columns(self) -> list: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.columns + + # Expects + expected_value = [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ] + assert actual == expected_value + + def test_is_skip(self) -> bool: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.is_skip + + # Expects + assert actual is True + + def test_is_skip_default(self) -> bool: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": "", + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.is_skip + + # Expects + assert actual is False + + def test_is_update_last_fetch_datetime(self) -> bool: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.is_update_last_fetch_datetime + + # Expects + assert actual is False + + def test_is_update_last_fetch_datetime_default(self) -> bool: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": "", + "last_fetch_datetime_file_name": "AccountShare.json", + "upload_file_name": "CRM_AccountShare", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.is_update_last_fetch_datetime + + # Expects + assert actual is True + + def test_last_fetch_datetime_file_name(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare_Test.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.last_fetch_datetime_file_name + + # Expects + assert actual == 'AccountShare_Test.json' + + def test_last_fetch_datetime_file_name_default(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "", + "upload_file_name": "CRM_AccountShare", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.last_fetch_datetime_file_name + + # Expects + assert actual == 'AccountShare.json' + + def test_upload_file_name(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare_Test.json", + "upload_file_name": "CRM_AccountShare_Test_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.upload_file_name + + # Expects + assert actual == f'CRM_AccountShare_Test_{execute_datetime.format_date()}' + + def test_upload_file_name_default(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare_Test.json", + "upload_file_name": "", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.upload_file_name + + # Expects + assert actual == f'CRM_AccountShare_{execute_datetime.format_date()}' + + def test_datetime_column(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare_Test.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "LastModifiedDate" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.datetime_column + + # Expects + assert actual == 'LastModifiedDate' + + def test_datetime_column_default(self) -> str: + """ + Cases: + オブジェクト情報から対象の値を返すこと + Arranges: + - 辞書型のオブジェクト情報を準備する + - 実行日時インスタンスを生成する + - オブジェクト情報インスタンスを生成する + Expects: + - 戻り値が期待値と一致する + """ + + # Arranges + object_info = { + "object_name": "AccountShare", + "columns": [ + "Id", + "AccountId", + "UserOrGroupId", + "AccountAccessLevel", + "OpportunityAccessLevel", + "CaseAccessLevel", + "ContactAccessLevel", + "RowCause", + "LastModifiedDate", + "LastModifiedById", + "IsDeleted" + ], + "is_skip": True, + "is_update_last_fetch_datetime": False, + "last_fetch_datetime_file_name": "AccountShare_Test.json", + "upload_file_name": "CRM_AccountShare_{execute_datetime}", + "datetime_column": "" + } + + execute_datetime = ExecuteDateTime() + + sut = TargetObject(object_info, execute_datetime) + + # Act + actual = sut.datetime_column + + # Expects + assert actual == 'SystemModstamp' diff --git a/ecs/crm-datafetch/tests/parser/test_json_parser.py b/ecs/crm-datafetch/tests/parser/test_json_parser.py new file mode 100644 index 00000000..c343752b --- /dev/null +++ b/ecs/crm-datafetch/tests/parser/test_json_parser.py @@ -0,0 +1,77 @@ +import pytest +from src.parser.json_parser import JsonParser + + +class TestJsonParser(): + + def test_parse(self) -> dict: + """ + Cases: + - コメントアウトが記載されているJSONからコメントを取り除き、辞書型を返すこと + Arranges: + - JSON文字列を準備する + Expects: + - json.loadsされたファイルの内容が期待値と一致する + """ + + # Arranges + json_string = """{ + "aaaa": "aaaa", + # これはコメントです + "#これはコメントではありません": "#これはコメントではありません", + "bbb": false, + "hogehoge": [ + "ccc", + /これはコメントです + "/これはコメントではありません" + ] + }""" + + # Act + sut = JsonParser(json_string) + actual = sut.parse() + + # Expects + expected_value = { + "aaaa": "aaaa", + "#これはコメントではありません": "#これはコメントではありません", + "bbb": False, + "hogehoge": [ + "ccc", + "/これはコメントではありません" + ] + } + + assert actual == expected_value + + def test_raise_parse(self) -> dict: + """ + Cases: + - コメントアウト記号ではない文字をコメントアウトとしたときに、例外が発生すること + Arranges: + - JSON文字列を準備する + Expects: + - 例外が発生し期待値と一致する + """ + + # Arranges + json_string = """{ + "aaaa": "aaaa", + $ これはコメントです + "#これはコメントではありません": "#これはコメントではありません", + "bbb": false, + "hogehoge": [ + "ccc", + /これはコメントです + "/これはコメントではありません" + ] + }""" + + # Act + with pytest.raises(Exception) as e: + + sut = JsonParser(json_string) + sut.parse() + + # Expects + assert "Expecting property name enclosed in double quotes:" in str(e.value) diff --git a/ecs/crm-datafetch/tests/test_controller.py b/ecs/crm-datafetch/tests/test_controller.py new file mode 100644 index 00000000..c142fcfd --- /dev/null +++ b/ecs/crm-datafetch/tests/test_controller.py @@ -0,0 +1,905 @@ +import logging +from copy import deepcopy +from unittest.mock import MagicMock, patch + +import pytest +from src import controller +from src.config.objects import (FetchTargetObjects, LastFetchDatetime, + TargetObject) +from src.error.exceptions import MeDaCaCRMDataFetchException +from src.system_var.constants import (CHK_JP_NAME, CONV_JP_NAME, CSVBK_JP_NAME, + DATE_JP_NAME, END_JP_NAME, FETCH_JP_NAME, + PRE_JP_NAME, RESBK_JP_NAME, UPD_JP_NAME, + UPLD_JP_NAME) +from src.util.execute_datetime import ExecuteDateTime + +from .test_utils.log_message import generate_log_message_tuple + +COMMON_OBJECT_INFOS = { + 'objects': [ + { + 'object_name': 'Account', + 'columns': ['Id'], + 'upload_file_name': 'Account_YYYYMMDDHHMMSS' + }, + { + 'object_name': 'Contact', + 'columns': ['Id'], + 'upload_file_name': 'Contact_YYYYMMDDHHMMSS' + }, + { + 'object_name': 'Call2_vod__c', + 'columns': ['Id'], + 'upload_file_name': 'Call2_vod__c_YYYYMMDDHHMMSS' + } + ] +} + +COMMON_EXECUTE_DATETIME = ExecuteDateTime() + +COMMON_FETCH_TARGET_OBJECTS = FetchTargetObjects(COMMON_OBJECT_INFOS) + +COMMON_TARGET_OBJECTS_1 = TargetObject(COMMON_OBJECT_INFOS['objects'][0], COMMON_EXECUTE_DATETIME) +COMMON_TARGET_OBJECTS_2 = TargetObject(COMMON_OBJECT_INFOS['objects'][1], COMMON_EXECUTE_DATETIME) +COMMON_TARGET_OBJECTS_3 = TargetObject(COMMON_OBJECT_INFOS['objects'][2], COMMON_EXECUTE_DATETIME) + +COMMON_LAST_FETCH_DATETIME = LastFetchDatetime({ + 'last_fetch_datetime_from': '1900-01-01T00:00:00.000Z', + 'last_fetch_datetime_to': '' +}, COMMON_EXECUTE_DATETIME) + + +class ForTestMeDaCaCRMDataFetchException(MeDaCaCRMDataFetchException): + def __init__(self, error_id: str, func_name: str, message: str) -> None: + super().__init__(error_id, func_name, message) + + +class ForTestException(Exception): + # カスタム例外とインタフェースを合わせるための引数 + def __init__(self, error_id: str, func_name: str, message: str) -> None: + super().__init__(message) + + +class TestController: + + @pytest.fixture(autouse=True) + def setup_teardown(self): + # setup + self.mock_prepare_data_fetch_process = MagicMock() + self.mock_check_object_info_process = MagicMock() + self.mock_set_datetime_period_process = MagicMock() + self.mock_fetch_crm_data_process = MagicMock() + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + # run test + yield + + # teardown + self.mock_prepare_data_fetch_process.reset_mock() + self.mock_check_object_info_process.reset_mock() + self.mock_set_datetime_period_process.reset_mock() + self.mock_fetch_crm_data_process.reset_mock() + self.mock_backup_crm_data_process.reset_mock() + self.mock_convert_crm_csv_data_process.reset_mock() + self.mock_backup_crm_csv_data_process.reset_mock() + self.mock_copy_crm_csv_data_process.reset_mock() + self.mock_upload_last_fetch_datetime_process.reset_mock() + self.mock_upload_result_data_process.reset_mock() + + @pytest.fixture() + def run_control_process(self): + + def _func(): + with patch('src.controller.prepare_data_fetch_process', self.mock_prepare_data_fetch_process),\ + patch('src.controller.check_object_info_process', self.mock_check_object_info_process),\ + patch('src.controller.set_datetime_period_process', self.mock_set_datetime_period_process),\ + patch('src.controller.fetch_crm_data_process', self.mock_fetch_crm_data_process),\ + patch('src.controller.backup_crm_data_process', self.mock_backup_crm_data_process),\ + patch('src.controller.convert_crm_csv_data_process', self.mock_convert_crm_csv_data_process),\ + patch('src.controller.backup_crm_csv_data_process', self.mock_backup_crm_csv_data_process),\ + patch('src.controller.copy_crm_csv_data_process', self.mock_copy_crm_csv_data_process),\ + patch('src.controller.upload_last_fetch_datetime_process', self.mock_upload_last_fetch_datetime_process),\ + patch('src.controller.upload_result_data_process', self.mock_upload_result_data_process): + controller.controller() + yield _func + + @pytest.fixture() + def call_all_processes(self, caplog, run_control_process): + """ + コントロール処理内ですべてのプロセス関数が呼ばれることのテストで使用するフィクスチャ + 各種プロセス関数をモック化し、正常終了させるように動く + + Yields: + dict: プロセス関数呼び出し後のモックの辞書 + """ + def _func(): + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(return_value=[{'Name': 'Test'}]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + yield _func + + def test_call_all_processes(self, call_all_processes): + """ + Cases: + コントロール処理からすべてのプロセスが呼ばれること + Arranges: + - call_all_processesフィクスチャで、コントロール処理を実行しておく + Expects: + - データ取得準備処理が1回のみ実行される + - オブジェクト情報形式チェック処理が複数回実行される + - データ取得期間設定処理が複数回実行される + - CRMデータ取得処理が複数回実行される + - CRM電文データバックアップ処理が複数回実行される + - CSV変換処理が複数回実行される + - CSVバックアップ処理が複数回実行される + - CSVアップロード処理が複数回実行される + - 前回取得日時ファイル更新処理が複数回実行される + - 取得処理実施結果アップロード処理が1回のみ実行される + """ + call_all_processes() + assert self.mock_prepare_data_fetch_process.called is True, 'データ取得準備処理が1回のみ実行されること' + assert self.mock_prepare_data_fetch_process.call_count == 1, 'データ取得準備処理が1回のみ実行されること' + assert self.mock_check_object_info_process.call_count == 3, 'オブジェクト情報形式チェック処理が複数回実行されること' + assert self.mock_set_datetime_period_process.call_count == 3, 'データ取得期間設定処理が複数回実行されること' + assert self.mock_fetch_crm_data_process.call_count == 3, 'CRMデータ取得処理が複数回実行されること' + assert self.mock_backup_crm_data_process.call_count == 3, 'CRM電文データバックアップ処理が複数回実行されること' + assert self.mock_convert_crm_csv_data_process.call_count == 3, 'CSV変換処理が複数回実行されること' + assert self.mock_backup_crm_csv_data_process.call_count == 3, 'CSVバックアップ処理が複数回実行されること' + assert self.mock_copy_crm_csv_data_process.call_count == 3, 'CSVアップロード処理が複数回実行されること' + assert self.mock_upload_last_fetch_datetime_process.call_count == 3, '前回取得日次ファイル更新処理が複数回実行されること' + assert self.mock_upload_result_data_process.called is True, '取得処理実施結果アップロード処理が1回のみ実行されること' + assert self.mock_upload_result_data_process.call_count == 1, '取得処理実施結果アップロード処理が1回のみ実行されること' + + def test_print_normal_logs(self, call_all_processes, caplog): + """ + Cases: + コントロール処理の正常系ログがすべての出力されていること + Arranges: + - call_all_processesフィクスチャで、コントロール処理を実行しておく + Expects: + - コントロール処理の正常系ログがすべて出力されている + """ + call_all_processes() + + assert generate_log_message_tuple(log_message='I-CTRL-01 CRMデータ取得処理を開始します') in caplog.record_tuples + assert generate_log_message_tuple(log_message='I-CTRL-02 データ取得準備処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message='I-CTRL-03 取得対象オブジェクトのループ処理開始') in caplog.record_tuples + for name in ['Account', 'Contact', 'Call2_vod__c']: + object_name = name + upload_file_name = f'{name}_YYYYMMDDHHMMSS' + assert generate_log_message_tuple(log_message='I-CTRL-05 オブジェクト情報形式チェック処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-06 [{object_name}]のデータ取得を開始します') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-08 [{object_name}]のデータ取得期間設定処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-09 [{object_name}]のデータ取得処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-10 [{object_name}] の出力ファイル名は [{upload_file_name}] となります') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-11 [{object_name}] CRM電文データバックアップ処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-12 [{object_name}] CSV変換処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-13 [{object_name}] CSVデータバックアップ処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-14 [{object_name}] CSVデータアップロード処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-15 [{object_name}] 前回取得日時ファイル更新処理呼び出し') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-16 [{object_name}] 処理正常終了') in caplog.record_tuples + + expect_process_result = { + 'Account': 'success', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-18 CRM_取得処理実施結果ファイルアップロード処理開始') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-19 すべてのデータの取得に成功しました') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples + + def test_do_not_call_upload_process_result_process(self, caplog, run_control_process): + """ + Cases: + 処理対象オブジェクトが0件の場合、取得処理実施結果アップロード処理が実行されないこと + Arranges: + - データ取得準備処理で返される取得対象オブジェクト情報を0件にする + - 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する + Expects: + - データ取得準備処理が1回のみ実行されること + - 取得処理実施結果アップロード処理が実行されないこと + - 処理対象が存在しない旨を示すログメッセージが出力されていること + """ + + self.mock_prepare_data_fetch_process = MagicMock(return_value=([], COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock() + self.mock_set_datetime_period_process = MagicMock() + self.mock_fetch_crm_data_process = MagicMock() + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + # 実行回数の確認 + assert self.mock_prepare_data_fetch_process.called, 'データ取得準備処理が1回のみ実行されること' + assert self.mock_prepare_data_fetch_process.call_count == 1, 'データ取得準備処理が1回のみ実行されること' + assert self.mock_upload_result_data_process.called is False, '取得処理実施結果アップロード処理が実行されないこと' + assert self.mock_upload_result_data_process.call_count == 0, '取得処理実施結果アップロード処理が実行されないこと' + + # ログ出力の確認 + assert generate_log_message_tuple(log_message='I-CTRL-21 処理対象のデータが存在しませんでした') in caplog.record_tuples, '処理対象が存在しない旨を示すログメッセージが出力されていること' + assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples + + def test_do_not_call_upload_csv_process_cause_is_skip_true(self, caplog, run_control_process): + """ + Cases: + オブジェクト情報.is_skipがTrueの場合、CSVアップロード処理が実行されないこと + Arranges: + - データ取得準備処理で返される取得対象オブジェクト情報のis_skipをTrueにする + - 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する + Expects: + - オブジェクト情報形式チェック処理以降のプロセスが実行されないこと + - 処理をスキップする旨を示すログメッセージが出力されていること + """ + mock_check_object_info = { + 'object_name': 'Account', + 'columns': ['id'], + 'is_skip': True + } + mock_return_values = [TargetObject(mock_check_object_info, COMMON_EXECUTE_DATETIME)] + self.mock_prepare_data_fetch_process = MagicMock(return_value=([mock_check_object_info], COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock() + self.mock_fetch_crm_data_process = MagicMock() + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + # 実行回数の確認 + + assert self.mock_check_object_info_process.called is True + assert self.mock_check_object_info_process.call_count == 1 + # オブジェクト情報形式チェック処理以降のプロセスが実行されないこと + assert self.mock_set_datetime_period_process.call_count == 0 + assert self.mock_fetch_crm_data_process.call_count == 0 + assert self.mock_backup_crm_data_process.call_count == 0 + assert self.mock_convert_crm_csv_data_process.call_count == 0 + assert self.mock_backup_crm_csv_data_process.call_count == 0 + assert self.mock_copy_crm_csv_data_process.call_count == 0 + assert self.mock_upload_last_fetch_datetime_process.call_count == 0 + # 結果ファイルの出力は行う + assert self.mock_upload_result_data_process.called is True + assert self.mock_upload_result_data_process.call_count == 1 + + # ログ出力の確認 + assert generate_log_message_tuple(log_message='I-CTRL-07 [Account]のデータ取得処理をスキップします') in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples + + def test_do_not_call_upload_csv_process_cause_is_skip_true_in_loop(self, caplog, run_control_process): + """ + Cases: + オブジェクト情報.is_skipがTrueのものが含まれる場合、対象オブジェクトのCSVアップロード処理が実行されないこと + Arranges: + - データ取得準備処理で返される取得対象オブジェクト情報のうち、2つ目をis_skipをTrueにする + - 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する + Expects: + - オブジェクト情報形式チェック処理が2回実行されること + - 処理をスキップする旨を示すログメッセージが出力されていること + - オブジェクト情報.is_skipがFalseのものはCSVアップロード処理のログメッセージが出力されていること + """ + mock_check_object_infos = { + 'objects': [ + { + 'object_name': 'Account', + 'columns': ['id'], + 'is_skip': False + }, + { + 'object_name': 'Contact', + 'columns': ['id'], + 'is_skip': True + }, + { + 'object_name': 'Call2_vod__c', + 'columns': ['id'], + 'is_skip': False + } + ] + } + FetchTargetObjects(mock_check_object_infos) + mock_return_values = [ + TargetObject(mock_check_object_infos['objects'][0], COMMON_EXECUTE_DATETIME), + TargetObject(mock_check_object_infos['objects'][1], COMMON_EXECUTE_DATETIME), + TargetObject(mock_check_object_infos['objects'][2], COMMON_EXECUTE_DATETIME), + ] + self.mock_prepare_data_fetch_process = MagicMock(return_value=(FetchTargetObjects(mock_check_object_infos), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock() + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{"Name": "Test"}], [{"Name": "Test"}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + # 実行回数の確認 + assert self.mock_prepare_data_fetch_process.called is True + assert self.mock_prepare_data_fetch_process.call_count == 1 + assert self.mock_check_object_info_process.call_count == 3 + # オブジェクト情報形式チェック処理以降のプロセスが実行されないこと + assert self.mock_set_datetime_period_process.call_count == 2 + assert self.mock_fetch_crm_data_process.call_count == 2 + assert self.mock_backup_crm_data_process.call_count == 2 + assert self.mock_convert_crm_csv_data_process.call_count == 2 + assert self.mock_backup_crm_csv_data_process.call_count == 2 + assert self.mock_copy_crm_csv_data_process.call_count == 2 + assert self.mock_upload_last_fetch_datetime_process.call_count == 2 + assert self.mock_upload_result_data_process.called is True + assert self.mock_upload_result_data_process.call_count == 1 + + # ログ出力の確認 + assert generate_log_message_tuple( + log_message='I-CTRL-14 [Account] CSVデータアップロード処理呼び出し') in caplog.record_tuples, 'オブジェクト情報.is_skipがFalseのものはCSVアップロード処理のログメッセージが出力されていること' + assert generate_log_message_tuple( + log_message='I-CTRL-07 [Contact]のデータ取得処理をスキップします') in caplog.record_tuples, '処理をスキップする旨を示すログメッセージが出力されていること' + assert generate_log_message_tuple( + log_message='I-CTRL-14 [Call2_vod__c] CSVデータアップロード処理呼び出し' + ) in caplog.record_tuples, 'オブジェクト情報.is_skipがFalseのものはCSVアップロード処理のログメッセージが出力されていること' + assert generate_log_message_tuple(log_message=f'I-CTRL-20 CRMデータ取得処理を終了します') in caplog.record_tuples + + def test_do_not_call_upload_csv_process_cause_crm_data_empty(self, caplog, run_control_process): + """ + Cases: + CRMデータ取得処理で取得できた件数が0件の場合、CSVアップロード処理が実行されないこと + Arranges: + - CRMデータ取得処理で返されるオブジェクトを空のリストにする + - 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する + Expects: + - CRM電文データバックアップ処理以降のプロセスが実行されないこと + - 処理をスキップする旨を示すログメッセージが出力されていること + """ + mock_check_object_infos = { + 'objects': [ + { + 'object_name': 'Account', + 'columns': ['id'], + 'is_skip': False + } + ] + } + + FetchTargetObjects(mock_check_object_infos) + mock_return_values = [ + TargetObject(mock_check_object_infos['objects'][0], COMMON_EXECUTE_DATETIME) + ] + self.mock_prepare_data_fetch_process = MagicMock(return_value=(FetchTargetObjects(mock_check_object_infos), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock() + self.mock_fetch_crm_data_process = MagicMock(return_value=[]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + # 実行回数の確認 + assert self.mock_prepare_data_fetch_process.called is True + assert self.mock_prepare_data_fetch_process.call_count == 1 + assert self.mock_check_object_info_process.call_count == 1 + assert self.mock_set_datetime_period_process.call_count == 1 + assert self.mock_fetch_crm_data_process.call_count == 1 + # CRM電文データバックアップ処理以降のプロセスが実行されないこと + assert self.mock_backup_crm_data_process.call_count == 0 + assert self.mock_convert_crm_csv_data_process.call_count == 0 + assert self.mock_backup_crm_csv_data_process.call_count == 0 + assert self.mock_copy_crm_csv_data_process.call_count == 0 + assert self.mock_upload_last_fetch_datetime_process.call_count == 0 + assert self.mock_upload_result_data_process.called is True + assert self.mock_upload_result_data_process.call_count == 1 + + # ログ出力の確認 + assert generate_log_message_tuple( + log_message='I-CTRL-22 [Account]のレコード件数が0件のため、ファイルアップロードをスキップします') in caplog.record_tuples, '処理をスキップする旨を示すログメッセージが出力されていること' + + def test_do_not_call_upload_csv_process_cause_crm_data_empty_in_loop(self, caplog, run_control_process): + """ + Cases: + CRMデータ取得処理で取得できた件数が0件のものが含まれる場合、対象オブジェクトのCSVアップロード処理が実行されないこと + Arranges: + - CRMデータ取得処理で返されるオブジェクトのうち、2つ目を空のリストにする + - 各種プロセスメソッドと内部で使用している値オブジェクトをモック化する + Expects: + - CRMデータ取得処理が2回実行されること + - 処理をスキップする旨を示すログメッセージが出力されていること + - 取得オブジェクトが1件以上取れているものはCSVアップロード処理のログメッセージが出力されていること + """ + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=[COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3]) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{"Name": "Test"}], [], [{"Name": "Test"}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + # 実行回数の確認 + assert self.mock_check_object_info_process.call_count == 3 + assert self.mock_set_datetime_period_process.call_count == 3 + assert self.mock_fetch_crm_data_process.call_count == 3 + # CRM電文データバックアップ処理以降のプロセスは件数があるもののみ実行されること + assert self.mock_backup_crm_data_process.call_count == 2 + assert self.mock_convert_crm_csv_data_process.call_count == 2 + assert self.mock_backup_crm_csv_data_process.call_count == 2 + assert self.mock_copy_crm_csv_data_process.call_count == 2 + assert self.mock_upload_last_fetch_datetime_process.call_count == 2 + assert self.mock_upload_result_data_process.called is True + assert self.mock_upload_result_data_process.call_count == 1 + + # ログ出力の確認 + assert generate_log_message_tuple( + log_message='I-CTRL-14 [Account] CSVデータアップロード処理呼び出し') in caplog.record_tuples, '取得オブジェクトが1件以上取れているものはCSVアップロード処理のログメッセージが出力されていること' + assert generate_log_message_tuple( + log_message='I-CTRL-22 [Contact]のレコード件数が0件のため、ファイルアップロードをスキップします') in caplog.record_tuples, '処理をスキップする旨を示すログメッセージが出力されていること' + assert generate_log_message_tuple( + log_message='I-CTRL-14 [Call2_vod__c] CSVデータアップロード処理呼び出し' + ) in caplog.record_tuples, '取得オブジェクトが1件以上取れているものはCSVアップロード処理のログメッセージが出力されていること' + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'E-ERR-01 [{PRE_JP_NAME}]でエラーが発生したため、処理を終了します'), + (ForTestException, 'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [例外発生]') + ]) + def test_raise_prepare_data_fetch_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. データ取得準備処理でシステム例外が発生した場合、エラーで終了すること + 2. データ取得準備処理で想定外の例外が発生した場合、エラーで終了すること + Arranges: + - パラメータ1:データ取得準備処理でシステム例外が発生するようにする + - パラメータ2:データ取得準備処理で想定外の例外が発生するようにする + Expects: + - データ取得準備処理で例外が発生すること + - データ取得準備処理で発生した例外のログメッセージが出力されていること + """ + expect_exception = exception('E-PRE-01', PRE_JP_NAME, '例外発生') + self.mock_prepare_data_fetch_process = MagicMock(side_effect=expect_exception) + self.mock_check_object_info_process = MagicMock() + with pytest.raises(exception): + run_control_process() + assert self.mock_check_object_info_process.called is False + assert generate_log_message_tuple( + log_level=logging.ERROR, + log_message=message) in caplog.record_tuples, 'データ取得準備処理で発生した例外のログメッセージが出力されていること' + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{CHK_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_check_object_info_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. オブジェクト情報形式チェック処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. オブジェクト情報形式チェック処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:オブジェクト情報形式チェック処理でシステム例外が発生するようにする + - パラメータ2:オブジェクト情報形式チェック処理で想定外の例外が発生するようにする + Expects: + - オブジェクト情報形式チェック処理で例外が発生する + - オブジェクト情報形式チェック処理で発生した例外のログメッセージが出力されている + - 例外が発生したオブジェクト以外は正常に終了する + """ + raise_object = exception('E-PRE-01', CHK_JP_NAME, '例外発生') + mock_return_values = [raise_object, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_2] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(return_value=[{'Name': 'Test'}]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + assert self.mock_check_object_info_process.call_count == 3 + assert self.mock_set_datetime_period_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{DATE_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_set_datetime_period_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. データ取得期間設定処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. データ取得期間設定処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:データ取得期間設定処理でシステム例外が発生するようにする + - パラメータ2:データ取得期間設定処理で想定外の例外が発生するようにする + Expects: + - データ取得期間設定チェック処理で例外が発生する + - データ取得期間設定チェック処理で発生した例外のログメッセージが出力されている + - 例外が発生したオブジェクト以外は正常に終了する + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(side_effect=[exception( + 'E-DATE-01', DATE_JP_NAME, '例外発生'), COMMON_LAST_FETCH_DATETIME, COMMON_LAST_FETCH_DATETIME]) + self.mock_fetch_crm_data_process = MagicMock(return_value=[{'Name': 'Test'}]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_set_datetime_period_process.call_count == 3 + assert self.mock_fetch_crm_data_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{FETCH_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_fetch_crm_data_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. CRMデータ取得処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. CRMデータ取得処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:CRMデータ取得処理でシステム例外が発生するようにする + - パラメータ2:CRMデータ取得処理で想定外の例外が発生するようにする + Expects: + - CRMデータ取得処理で例外が発生すること + - CRMデータ取得処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[exception( + 'E-FETCH-01', FETCH_JP_NAME, '例外発生'), [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_fetch_crm_data_process.call_count == 3 + assert self.mock_backup_crm_data_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{RESBK_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_backup_crm_data_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. CRM電文データバックアップ処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. CRM電文データバックアップ処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:CRM電文データバックアップ処理でシステム例外が発生するようにする + - パラメータ2:CRM電文データバックアップ処理で想定外の例外が発生するようにする + Expects: + - CRM電文データバックアップ処理で例外が発生すること + - CRM電文データバックアップ処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock(side_effect=[exception( + 'E-RESBK-01', RESBK_JP_NAME, '例外発生'), None, None]) + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_backup_crm_data_process.call_count == 3 + assert self.mock_convert_crm_csv_data_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{CONV_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_convert_crm_csv_data_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. CSV変換処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. CSV変換処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:CSV変換処理でシステム例外が発生するようにする + - パラメータ2:CSV変換処理で想定外の例外が発生するようにする + Expects: + - CSV変換処理で例外が発生すること + - CSV変換処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock(side_effect=[exception( + 'E-CONV-01', CONV_JP_NAME, '例外発生'), None, None]) + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_convert_crm_csv_data_process.call_count == 3 + assert self.mock_backup_crm_csv_data_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{CSVBK_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_backup_crm_csv_data_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. CSVバックアップ処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. CSVバックアップ処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:CSVバックアップ処理でシステム例外が発生するようにする + - パラメータ2:CSVバックアップ処理で想定外の例外が発生するようにする + Expects: + - CSVバックアップ処理で例外が発生すること + - CSVバックアップ処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock(side_effect=[exception( + 'E-CSVBK-01', CSVBK_JP_NAME, '例外発生'), None, None]) + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_backup_crm_csv_data_process.call_count == 3 + assert self.mock_copy_crm_csv_data_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{UPLD_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_copy_crm_csv_data_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. CSVアップロード処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. CSVアップロード処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:CSVアップロード処理でシステム例外が発生するようにする + - パラメータ2:CSVアップロード処理で想定外の例外が発生するようにする + Expects: + - CSVアップロード処理で例外が発生すること + - CSVアップロード処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock(side_effect=[exception( + 'E-UPLD-01', UPLD_JP_NAME, '例外発生'), None, None]) + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_copy_crm_csv_data_process.call_count == 3 + assert self.mock_upload_last_fetch_datetime_process.call_count == 2 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'I-ERR-03 [Account] の[{UPD_JP_NAME}]でエラーが発生しました 次のオブジェクトの処理に移行します'), + (ForTestException, 'I-ERR-04 [Account] の処理中に予期せぬエラーが発生しました 次のオブジェクトの処理に移行します エラー内容: [例外発生]') + ]) + def test_raise_upload_last_fetch_datetime_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. 前回取得日時ファイル更新処理でシステム例外が発生した場合、エラーログが送出され、後続の終了に移行すること + 2. 前回取得日時ファイル更新処理で想定外の例外が発生した場合、エラーログが送出され、後続の終了に移行すること + Arranges: + - パラメータ1:前回取得日時ファイル更新処理でシステム例外が発生するようにする + - パラメータ2:前回取得日時ファイル更新処理で想定外の例外が発生するようにする + Expects: + - 前回取得日時ファイル更新処理で例外が発生すること + - 前回取得日時ファイル更新処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock(side_effect=[exception( + 'E-UPD-01', UPD_JP_NAME, '例外発生'), None, None]) + self.mock_upload_result_data_process = MagicMock() + + run_control_process() + + assert self.mock_upload_last_fetch_datetime_process.call_count == 3 + expect_process_result = { + 'Account': 'fail', + 'Contact': 'success', + 'Call2_vod__c': 'success' + } + assert generate_log_message_tuple(log_message=message) in caplog.record_tuples + assert generate_log_message_tuple(log_message=f'I-CTRL-17 すべてのオブジェクトの処理が終了しました 実行結果:[{expect_process_result}]') in caplog.record_tuples + assert generate_log_message_tuple( + log_level=logging.ERROR, log_message=f'E-CTRL-01 一部のデータ取得に失敗しています 詳細はログをご確認ください') in caplog.record_tuples + + @pytest.mark.parametrize( + 'exception, message', + [ + (ForTestMeDaCaCRMDataFetchException, f'E-ERR-01 [{END_JP_NAME}]でエラーが発生したため、処理を終了します'), + (ForTestException, 'E-ERR-02 予期せぬエラーが発生したため、処理を終了します エラー内容: [例外発生]') + ]) + def test_raise_upload_result_data_process(self, caplog, exception, message, run_control_process): + """ + Cases: + 1. 取得処理実施結果アップロード処理でシステム例外が発生した場合、エラーで終了すること + 2. 取得処理実施結果アップロード処理で想定外の例外が発生した場合、エラーで終了すること + Arranges: + - パラメータ1:取得処理実施結果アップロード処理でシステム例外が発生するようにする + - パラメータ2:取得処理実施結果アップロード処理で想定外の例外が発生するようにする + Expects: + - 取得処理実施結果アップロード処理で例外が発生すること + - 取得処理実施結果アップロード処理で発生した例外のログメッセージが出力されていること + """ + mock_return_values = [COMMON_TARGET_OBJECTS_1, COMMON_TARGET_OBJECTS_2, COMMON_TARGET_OBJECTS_3] + + self.mock_prepare_data_fetch_process = MagicMock(return_value=(deepcopy(COMMON_FETCH_TARGET_OBJECTS), COMMON_EXECUTE_DATETIME, {})) + self.mock_check_object_info_process = MagicMock(side_effect=mock_return_values) + self.mock_set_datetime_period_process = MagicMock(return_value=COMMON_LAST_FETCH_DATETIME) + self.mock_fetch_crm_data_process = MagicMock(side_effect=[[{'Name': 'Test'}], [{'Name': 'Test'}], [{'Name': 'Test'}]]) + self.mock_backup_crm_data_process = MagicMock() + self.mock_convert_crm_csv_data_process = MagicMock() + self.mock_backup_crm_csv_data_process = MagicMock() + self.mock_copy_crm_csv_data_process = MagicMock() + self.mock_upload_last_fetch_datetime_process = MagicMock() + self.mock_upload_result_data_process = MagicMock(side_effect=[exception( + 'E-END-01', END_JP_NAME, '例外発生'), None, None]) + + with pytest.raises(exception): + run_control_process() + + assert self.mock_upload_result_data_process.called is True + assert self.mock_upload_result_data_process.call_count == 1 + assert generate_log_message_tuple( + log_level=logging.ERROR, + log_message=message) in caplog.record_tuples, '取得処理実施結果アップロード処理で発生した例外のログメッセージが出力されていること' diff --git a/ecs/crm-datafetch/tests/util/test_counter_object.py b/ecs/crm-datafetch/tests/util/test_counter_object.py new file mode 100644 index 00000000..787099ba --- /dev/null +++ b/ecs/crm-datafetch/tests/util/test_counter_object.py @@ -0,0 +1,213 @@ +import pytest +from src.util.counter_object import CounterObject + + +class TestCounterObject: + + def test_constructor(self) -> int: + """ + Cases: + カウンターオブジェクト生成時に、数値を渡すと例外が発生しないこと + Arranges: + なし + Expects: + 例外が発生しないこと + """ + # Act + CounterObject(1) + + # Expects + pass + + def test_constructor_string_number(self) -> int: + """ + Cases: + カウンターオブジェクト生成時に、文字列型の数値を渡すと例外が発生しないこと + Arranges: + なし + Expects: + 例外が発生しないこと + """ + # Act + CounterObject("1") + + # Expects + pass + + def test_raise_constructor_string(self) -> int: + """ + Cases: + カウンターオブジェクト生成時に、文字列を渡すと例外が発生すること + Arranges: + なし + Expects: + 発生した例外が期待値と一致すること + """ + # Act + with pytest.raises(Exception) as e: + CounterObject("test1") + + # Expects + assert "invalid literal for int() with base 10:" in str(e.value) + + def test_describe(self) -> int: + """ + Cases: + カウンターオブジェクトにて保持した値を返すこと(インスタンス生成時引数なし) + Arranges: + なし + Expects: + 問い合わせた値が期待値と一致する + """ + + # Act + sut = CounterObject() + actual = sut.describe() + + # Expects + assert actual == 1 + + def test_describe_argument(self) -> int: + """ + Cases: + カウンターオブジェクトにて保持した値を返すこと(インスタンス生成時引数あり) + Arranges: + なし + Expects: + 問い合わせた値が期待値と一致する + """ + + # Act + sut = CounterObject(3) + actual = sut.describe() + + # Expects + assert actual == 3 + + def test_raise_describe(self) -> int: + """ + Cases: + カウンターオブジェクトの保持した値を問い合わせる際、引数を渡すと例外が発生すること + Arranges: + なし + Expects: + 問い合わせた値が期待値と一致する + """ + + # Act + with pytest.raises(Exception) as e: + sut = CounterObject() + sut.describe(1) + + # Expects + assert str(e.value) == 'describe() takes 1 positional argument but 2 were given' + + def test_increment(self) -> int: + """ + Cases: + カウンターオブジェクトにて保持した値がインクリメントされていること(引数なし) + Arranges: + なし + Expects: + 戻り値が期待値と一致する + """ + + # Act + sut = CounterObject() + sut.increment() + actual = sut.increment() + + # Expects + assert actual == 3 + + def test_increment_argument(self) -> int: + """ + Cases: + カウンターオブジェクトにて保持した値がインクリメントされていること(引数あり) + Arranges: + なし + Expects: + 戻り値が期待値と一致する + """ + + # Act + sut = CounterObject(5) + sut.increment(2) + actual = sut.increment(2) + + # Expects + assert actual == 9 + + def test_raise_increment(self) -> int: + """ + Cases: + 文字列を引数で渡すことで、例外が発生すること + Arranges: + なし + Expects: + 発生した例外が期待値と一致する + """ + + # Act + with pytest.raises(Exception) as e: + sut = CounterObject(5) + sut.increment('aaa') + sut.increment('aaa') + + # Expects + assert str(e.value) == "unsupported operand type(s) for +=: 'int' and 'str'" + + def test_decrement(self) -> int: + """ + Cases: + カウンターオブジェクトにて保持した値がデクリメントされていること(引数なし) + Arranges: + なし + Expects: + 戻り値が期待値と一致する + """ + + # Act + sut = CounterObject() + sut.decrement() + actual = sut.decrement() + + # Expects + assert actual == -1 + + def test_decrement_argument(self) -> int: + """ + Cases: + カウンターオブジェクトにて保持した値がデクリメントされていること(引数あり) + Arranges: + なし + Expects: + 戻り値が期待値と一致する + """ + + # Act + sut = CounterObject(5) + sut.decrement(2) + actual = sut.decrement(2) + + # Expects + assert actual == 1 + + def test_raise_decrement(self) -> int: + """ + Cases: + 文字列を引数で渡すことで、例外が発生すること + Arranges: + なし + Expects: + 発生した例外が期待値と一致する + """ + + # Act + with pytest.raises(Exception) as e: + sut = CounterObject(5) + sut.decrement('aaa') + sut.decrement('aaa') + + # Expects + assert str(e.value) == "unsupported operand type(s) for -=: 'int' and 'str'"