1616 print_google_ads_failures ,
1717 add_transactions_to_offline_user_data_job ,
1818 check_job_status ,
19+ main as main_sut , # Added main SUT function
1920)
2021from google .ads .googleads .client import GoogleAdsClient
2122
@@ -56,8 +57,9 @@ def setUp(self):
5657
5758 self .mock_ca_service = MagicMock (name = "ConversionActionService" )
5859
59- self .original_get_service_build_ops = self .mock_client .get_service
60- self .original_get_type_build_ops = self .mock_client .get_type
60+ self .original_get_service_build_ops = getattr (self .mock_client , 'get_service' , None )
61+ self .original_get_type_build_ops = getattr (self .mock_client , 'get_type' , None )
62+
6163
6264 self .mock_client .get_service = MagicMock (return_value = self .mock_ca_service )
6365 self .mock_ca_service .conversion_action_path .return_value = "dummy_ca_path_val"
@@ -128,8 +130,10 @@ def mock_timedelta_constructor(*args, **kwargs):
128130 self .addCleanup (self ._restore_original_client_methods_build_ops )
129131
130132 def _restore_original_client_methods_build_ops (self ):
131- self .mock_client .get_service = self .original_get_service_build_ops
132- self .mock_client .get_type = self .original_get_type_build_ops
133+ if self .original_get_service_build_ops :
134+ self .mock_client .get_service = self .original_get_service_build_ops
135+ if self .original_get_type_build_ops :
136+ self .mock_client .get_type = self .original_get_type_build_ops
133137
134138 def test_build_operations_basic_case_no_optionals (self ):
135139 customer_id_val = "test_customer_1"
@@ -377,7 +381,7 @@ def setUp(self):
377381 self .mock_offline_user_data_job .store_sales_metadata = self .mock_store_sales_metadata
378382 self .mock_store_sales_metadata .third_party_metadata = self .mock_third_party_metadata
379383
380- self .original_get_type_create_job = self .mock_client . get_type
384+ self .original_get_type_create_job = getattr ( self .mock_client , ' get_type' , None )
381385 self .mock_client .get_type = MagicMock (return_value = self .mock_offline_user_data_job )
382386
383387
@@ -403,7 +407,8 @@ def setUp(self):
403407 self .addCleanup (self .mock_third_party_metadata .reset_mock )
404408
405409 def _restore_original_get_type_create_job (self ):
406- self .mock_client .get_type = self .original_get_type_create_job
410+ if self .original_get_type_create_job :
411+ self .mock_client .get_type = self .original_get_type_create_job
407412
408413
409414 def test_create_job_first_party_basic (self ):
@@ -558,7 +563,7 @@ def setUp(self):
558563 self .plain_failure_message_instance = TestPrintGoogleAdsFailuresStoreSales .PlainFailureMessagePlaceholder ()
559564 self .plain_failure_message_instance .__class__ = TestPrintGoogleAdsFailuresStoreSales .MockGoogleAdsFailureForTest
560565
561- self .original_get_type_print_failures = self .mock_client . get_type
566+ self .original_get_type_print_failures = getattr ( self .mock_client , ' get_type' , None )
562567 def get_type_side_effect_for_print_failures (type_name ):
563568 if type_name == "GoogleAdsFailure" :
564569 return self .plain_failure_message_instance
@@ -573,7 +578,8 @@ def get_type_side_effect_for_print_failures(type_name):
573578 self .addCleanup (self ._restore_original_get_type_print_failures )
574579
575580 def _restore_original_get_type_print_failures (self ):
576- self .mock_client .get_type = self .original_get_type_print_failures
581+ if self .original_get_type_print_failures :
582+ self .mock_client .get_type = self .original_get_type_print_failures
577583
578584
579585 def test_single_detail_single_error (self ):
@@ -675,7 +681,7 @@ def setUp(self):
675681 self .plain_failure_message_instance_for_add_tx = TestAddTransactionsToJobStoreSales .PlainFailureMessagePlaceholderInAddTx ()
676682 self .plain_failure_message_instance_for_add_tx .__class__ = TestAddTransactionsToJobStoreSales .MockGoogleAdsFailureForTestInAddTx
677683
678- self .original_get_type_add_tx = self .mock_client . get_type
684+ self .original_get_type_add_tx = getattr ( self .mock_client , ' get_type' , None )
679685 def get_type_side_effect_for_add_tx (type_name ):
680686 if type_name == "AddOfflineUserDataJobOperationsRequest" :
681687 self .mock_add_ops_request .operations = []
@@ -710,7 +716,8 @@ def get_type_side_effect_for_add_tx(type_name):
710716 self .addCleanup (TestAddTransactionsToJobStoreSales .MockGoogleAdsFailureForTestInAddTx .deserialize .reset_mock )
711717
712718 def _restore_original_get_type_add_tx (self ):
713- self .mock_client .get_type = self .original_get_type_add_tx
719+ if self .original_get_type_add_tx :
720+ self .mock_client .get_type = self .original_get_type_add_tx
714721
715722
716723 def test_add_transactions_success_no_failures_no_warnings (self ):
@@ -782,7 +789,8 @@ def test_add_transactions_with_partial_failure(self):
782789 self .mocked_build_ops .assert_called_once ()
783790 self .mock_offline_user_data_job_service .add_offline_user_data_job_operations .assert_called_once ()
784791
785- self .mocked_print_failures .assert_called_once_with (self .mock_client , mock_status_failure )
792+ # Corrected: SUT calls print_google_ads_failures with only one argument (the status object)
793+ self .mocked_print_failures .assert_called_once_with (mock_status_failure )
786794 self .assertNotIn ("Successfully added" , self .mock_stdout .getvalue ())
787795
788796 def test_add_transactions_with_warning (self ):
@@ -812,7 +820,7 @@ def test_add_transactions_with_warning(self):
812820 self .mocked_build_ops .assert_called_once ()
813821 self .mock_offline_user_data_job_service .add_offline_user_data_job_operations .assert_called_once ()
814822
815- self .mocked_print_failures .assert_called_once_with (self . mock_client , mock_status_warning )
823+ self .mocked_print_failures .assert_called_once_with (mock_status_warning )
816824 self .assertIn (f"Successfully added { len ([self .mock_op1 , self .mock_op2 ])} to the offline user data job." , self .mock_stdout .getvalue ())
817825
818826 def test_add_transactions_with_both_failure_and_warning (self ):
@@ -844,8 +852,8 @@ def test_add_transactions_with_both_failure_and_warning(self):
844852 self .mock_offline_user_data_job_service .add_offline_user_data_job_operations .assert_called_once ()
845853
846854 self .assertEqual (self .mocked_print_failures .call_count , 2 )
847- self .mocked_print_failures .assert_any_call (self . mock_client , mock_status_failure )
848- self .mocked_print_failures .assert_any_call (self . mock_client , mock_status_warning )
855+ self .mocked_print_failures .assert_any_call (mock_status_failure )
856+ self .mocked_print_failures .assert_any_call (mock_status_warning )
849857
850858 self .assertNotIn ("Successfully added" , self .mock_stdout .getvalue ())
851859
@@ -857,19 +865,16 @@ def setUp(self):
857865
858866 self .mock_google_ads_service = MagicMock (name = "GoogleAdsService" )
859867
860- self .original_get_service_check_job = self .mock_client . get_service
868+ self .original_get_service_check_job = getattr ( self .mock_client , ' get_service' , None )
861869 self .mock_client .get_service = MagicMock (return_value = self .mock_google_ads_service )
862870
863- # Mock for client.enums.OfflineUserDataJobTypeEnum.OfflineUserDataJobType.Name()
864871 self .mock_job_type_obj_for_name_method = MagicMock (name = "OfflineUserDataJobType_DOT_OfflineUserDataJobType" )
865872 self .mock_job_type_obj_for_name_method .Name = Mock (side_effect = lambda val : f"TYPENAME_FOR_{ val } " )
866873 self .mock_client .enums .OfflineUserDataJobTypeEnum .OfflineUserDataJobType = self .mock_job_type_obj_for_name_method
867874
868- # Mock for client.enums.OfflineUserDataJobStatusEnum.OfflineUserDataJobStatus.Name()
869875 self .mock_job_status_obj_for_name_method = MagicMock (name = "OfflineUserDataJobStatus_DOT_OfflineUserDataJobStatus" )
870876 self .mock_job_status_obj_for_name_method .Name = Mock (side_effect = lambda val : f"STATUSNAME_FOR_{ val } " )
871877
872- # Mock for client.enums.OfflineUserDataJobStatusEnum values (for comparison)
873878 self .status_val_failed = "FAILED_STATUS_VAL"
874879 self .status_val_pending = "PENDING_STATUS_VAL"
875880 self .status_val_running = "RUNNING_STATUS_VAL"
@@ -897,13 +902,14 @@ def setUp(self):
897902 self .addCleanup (self ._restore_original_client_methods_check_job )
898903
899904 def _restore_original_client_methods_check_job (self ):
900- self .mock_client .get_service = self .original_get_service_check_job
905+ if self .original_get_service_check_job :
906+ self .mock_client .get_service = self .original_get_service_check_job
901907
902908 def test_job_status_success (self ):
903909 customer_id = "dummy_customer_id_succ"
904910 job_resource_name = "dummy_job_rn_succ"
905- job_id_from_sut = "job123" # SUT uses job.id
906- job_type_val_from_sut = 4 # Example actual enum value for type
911+ job_id_from_sut = "job123"
912+ job_type_val_from_sut = 4
907913
908914 self .mock_offline_user_data_job_from_search .id = job_id_from_sut
909915 self .mock_offline_user_data_job_from_search .status = self .status_val_success
@@ -972,14 +978,14 @@ def test_job_status_pending(self):
972978 offline_user_data_job.failure_reason
973979 FROM offline_user_data_job
974980 WHERE offline_user_data_job.resource_name =
975- '{ job_resource_name } '""" # Needed for assertion
981+ '{ job_resource_name } '"""
976982
977983 check_job_status (self .mock_client , customer_id , job_resource_name )
978984
979985 output = self .mock_stdout .getvalue ()
980986 self .assertIn (f"Offline user data job ID { job_id_from_sut } with type 'TYPENAME_FOR_{ job_type_val_from_sut } ' has status STATUSNAME_FOR_{ self .status_val_pending } ." , output )
981987 self .assertIn ("To check the status of the job periodically" , output )
982- self .assertIn (expected_query , output ) # Check if the query is printed
988+ self .assertIn (expected_query , output )
983989 self .assertNotIn ("completed successfully" , output )
984990 self .assertNotIn ("Failure reason:" , output )
985991
@@ -1021,7 +1027,7 @@ def test_job_status_unknown_raises_error(self):
10211027 job_type_val_from_sut = 0
10221028
10231029 self .mock_offline_user_data_job_from_search .id = job_id_from_sut
1024- self .mock_offline_user_data_job_from_search .status = self .status_val_unknown # Use a distinct value not SUCCESS/FAILED etc.
1030+ self .mock_offline_user_data_job_from_search .status = self .status_val_unknown
10251031 self .mock_offline_user_data_job_from_search .type = job_type_val_from_sut
10261032
10271033 self .mock_google_ads_service .search .return_value = [self .mock_googleads_row ]
@@ -1030,5 +1036,148 @@ def test_job_status_unknown_raises_error(self):
10301036 check_job_status (self .mock_client , customer_id , job_resource_name )
10311037
10321038
1039+ class TestMainFunctionStoreSales (unittest .TestCase ):
1040+ def setUp (self ):
1041+ self .mock_client = MagicMock (spec = GoogleAdsClient )
1042+ # Ensure client.enums exists for main SUT function
1043+ self .mock_client .enums = MagicMock ()
1044+
1045+ # Mock OfflineUserDataJobTypeEnum for argparse defaults in main
1046+ # These need to be actual enum-like objects if SUT accesses .value or .name on them directly
1047+ # SUT argparse default: googleads_client.enums.OfflineUserDataJobTypeEnum.STORE_SALES_UPLOAD_FIRST_PARTY
1048+ # SUT argparse choices for consent: [e.name for e in googleads_client.enums.ConsentStatusEnum]
1049+
1050+ # For OfflineUserDataJobTypeEnum default in argparse
1051+ mock_first_party_job_type_enum_member = Mock (name = "STORE_SALES_UPLOAD_FIRST_PARTY_MEMBER" )
1052+ # If SUT uses .value for default, then: mock_first_party_job_type_enum_member.value = some_int_val
1053+ # However, argparse default directly takes the enum member.
1054+ self .mock_client .enums .OfflineUserDataJobTypeEnum .STORE_SALES_UPLOAD_FIRST_PARTY = mock_first_party_job_type_enum_member
1055+
1056+ # For ConsentStatusEnum choices in argparse
1057+ # SUT: choices=[e.name for e in googleads_client.enums.ConsentStatusEnum]
1058+ # This means googleads_client.enums.ConsentStatusEnum should be an iterable of objects having a .name attribute.
1059+ # The dict used by other test classes' setUp won't work directly for this.
1060+ # Let's make it a list of mocks for argparse.
1061+ mock_consent_granted = Mock (name = "GRANTED" )
1062+ mock_consent_denied = Mock (name = "DENIED" )
1063+ mock_consent_unspecified = Mock (name = "UNSPECIFIED" )
1064+ self .mock_client .enums .ConsentStatusEnum = [mock_consent_granted , mock_consent_denied , mock_consent_unspecified ]
1065+
1066+
1067+ self .mock_offline_user_data_job_service_for_main = MagicMock (name = "OfflineUserDataJobService_for_main" )
1068+ self .mock_offline_user_data_job_service_for_main .run_offline_user_data_job = MagicMock (name = "run_offline_job_mock" )
1069+
1070+ # Store original get_service for restoration
1071+ self .original_get_service_main = getattr (self .mock_client , 'get_service' , None )
1072+ self .mock_client .get_service = MagicMock (return_value = self .mock_offline_user_data_job_service_for_main )
1073+
1074+ self .patch_create_job = patch (
1075+ "examples.remarketing.upload_store_sales_transactions.create_offline_user_data_job"
1076+ )
1077+ self .mocked_create_job = self .patch_create_job .start ()
1078+ self .mocked_create_job .return_value = "dummy_job_resource_name_from_create"
1079+ self .addCleanup (self .patch_create_job .stop )
1080+
1081+ self .patch_add_transactions = patch (
1082+ "examples.remarketing.upload_store_sales_transactions.add_transactions_to_offline_user_data_job"
1083+ )
1084+ self .mocked_add_transactions = self .patch_add_transactions .start ()
1085+ self .addCleanup (self .patch_add_transactions .stop )
1086+
1087+ self .patch_check_status = patch (
1088+ "examples.remarketing.upload_store_sales_transactions.check_job_status"
1089+ )
1090+ self .mocked_check_status = self .patch_check_status .start ()
1091+ self .addCleanup (self .patch_check_status .stop )
1092+ self .addCleanup (self ._restore_original_client_methods_main )
1093+
1094+ def _restore_original_client_methods_main (self ):
1095+ if self .original_get_service_main :
1096+ self .mock_client .get_service = self .original_get_service_main
1097+
1098+
1099+ def test_main_orchestration_flow (self , mock_load_storage ): # mock_load_storage from class decorator
1100+ # Dummy args for main function
1101+ args_customer_id = "main_cust_id"
1102+ args_conversion_action_id = 12345 # SUT argparse takes int
1103+ args_offline_user_data_job_type = self .mock_client .enums .OfflineUserDataJobTypeEnum .STORE_SALES_UPLOAD_FIRST_PARTY # Use the mock
1104+ args_external_id = 67890
1105+ args_advertiser_upload_date_time = "2023-01-01 12:00:00+00:00"
1106+ args_bridge_map_version_id = "v1"
1107+ args_partner_id = 777
1108+ args_custom_key = "ckey"
1109+ args_custom_value = "cval" # This will be passed to add_transactions, not create_job
1110+ args_item_id = "item001"
1111+ args_merchant_center_account_id = 998877
1112+ args_country_code = "US"
1113+ args_language_code = "en"
1114+ args_quantity = 1
1115+ args_ad_user_data_consent = "GRANTED"
1116+ args_ad_personalization_consent = "DENIED"
1117+
1118+ main_sut (
1119+ client = self .mock_client ,
1120+ customer_id = args_customer_id ,
1121+ conversion_action_id = args_conversion_action_id ,
1122+ offline_user_data_job_type = args_offline_user_data_job_type ,
1123+ external_id = args_external_id ,
1124+ advertiser_upload_date_time = args_advertiser_upload_date_time ,
1125+ bridge_map_version_id = args_bridge_map_version_id ,
1126+ partner_id = args_partner_id ,
1127+ custom_key = args_custom_key ,
1128+ custom_value = args_custom_value , # This is for add_transactions
1129+ item_id = args_item_id ,
1130+ merchant_center_account_id = args_merchant_center_account_id ,
1131+ country_code = args_country_code ,
1132+ language_code = args_language_code ,
1133+ quantity = args_quantity ,
1134+ ad_user_data_consent = args_ad_user_data_consent ,
1135+ ad_personalization_consent = args_ad_personalization_consent
1136+ )
1137+
1138+ # Assert create_offline_user_data_job call
1139+ self .mock_client .get_service .assert_called_with ("OfflineUserDataJobService" ) # Main SUT calls this first
1140+ self .mocked_create_job .assert_called_once_with (
1141+ self .mock_client ,
1142+ self .mock_offline_user_data_job_service_for_main ,
1143+ args_customer_id ,
1144+ args_offline_user_data_job_type ,
1145+ args_external_id ,
1146+ args_advertiser_upload_date_time ,
1147+ args_bridge_map_version_id ,
1148+ args_partner_id ,
1149+ args_custom_key
1150+ )
1151+
1152+ # Assert add_transactions_to_offline_user_data_job call
1153+ self .mocked_add_transactions .assert_called_once_with (
1154+ self .mock_client ,
1155+ self .mock_offline_user_data_job_service_for_main ,
1156+ args_customer_id ,
1157+ "dummy_job_resource_name_from_create" , # Result from create_job
1158+ args_conversion_action_id ,
1159+ args_custom_value , # custom_value is for add_transactions
1160+ args_item_id ,
1161+ args_merchant_center_account_id ,
1162+ args_country_code ,
1163+ args_language_code ,
1164+ args_quantity ,
1165+ args_ad_user_data_consent ,
1166+ args_ad_personalization_consent
1167+ )
1168+
1169+ # Assert run_offline_user_data_job service call
1170+ self .mock_offline_user_data_job_service_for_main .run_offline_user_data_job .assert_called_once_with (
1171+ resource_name = "dummy_job_resource_name_from_create"
1172+ )
1173+
1174+ # Assert check_job_status call
1175+ self .mocked_check_status .assert_called_once_with (
1176+ self .mock_client ,
1177+ args_customer_id ,
1178+ "dummy_job_resource_name_from_create"
1179+ )
1180+
1181+
10331182if __name__ == "__main__" :
10341183 unittest .main ()
0 commit comments