1111from django .core import mail
1212from django .test import SimpleTestCase , override_settings , tag
1313from django .utils .timezone import get_fixed_timezone , override as override_current_timezone
14+ from mock import patch
1415
1516from anymail .exceptions import (AnymailAPIError , AnymailConfigurationError , AnymailSerializationError ,
1617 AnymailUnsupportedFeature , AnymailWarning )
@@ -32,6 +33,13 @@ class SendGridBackendMockAPITestCase(RequestsBackendMockAPITestCase):
3233
3334 def setUp (self ):
3435 super (SendGridBackendMockAPITestCase , self ).setUp ()
36+
37+ # Patch uuid4 to generate predictable anymail_ids for testing
38+ patch_uuid4 = patch ('anymail.backends.sendgrid.uuid.uuid4' ,
39+ side_effect = ["mocked-uuid-%d" % n for n in range (1 , 5 )])
40+ patch_uuid4 .start ()
41+ self .addCleanup (patch_uuid4 .stop )
42+
3543 # Simple message useful for many tests
3644 self .
message = mail .
EmailMultiAlternatives (
'Subject' ,
'Text Body' ,
'[email protected] ' , [
'[email protected] ' ])
3745
@@ -57,7 +65,7 @@ def test_send_mail(self):
5765 'to' : [{
'email' :
"[email protected] " }],
5866 }])
5967 # make sure the backend assigned the anymail_id for event tracking and notification
60- self .assertUUIDIsValid (data ['custom_args' ]['anymail_id' ])
68+ self .assertEqual (data ['custom_args' ]['anymail_id' ], 'mocked-uuid-1' )
6169
6270 def test_name_addr (self ):
6371 """Make sure RFC2822 name-addr format (with display-name) is allowed
@@ -118,7 +126,7 @@ def test_email_message(self):
118126 'Message-ID' :
"<[email protected] >" ,
119127 })
120128 # make sure custom Message-ID also added to custom_args
121- self .assertUUIDIsValid (data ['custom_args' ]['anymail_id' ])
129+ self .assertEqual (data ['custom_args' ]['anymail_id' ], 'mocked-uuid-1' )
122130
123131 def test_html_message (self ):
124132 text_content = 'This is an important message.'
@@ -573,6 +581,122 @@ def test_legacy_warn_if_no_global_merge_field_delimiters(self):
573581 with self .assertWarnsRegex (AnymailWarning , r'SENDGRID_MERGE_FIELD_FORMAT' ):
574582 self .message .send ()
575583
584+ def test_merge_metadata (self ):
585+ 586+ self .message .merge_metadata = {
587+ '[email protected] ' : {
'order_id' :
123 },
588+ '[email protected] ' : {
'order_id' :
678 ,
'tier' :
'premium' },
589+ }
590+ self .message .send ()
591+ data = self .get_api_call_json ()
592+ self .assertEqual (data ['personalizations' ], [
593+ {
'to' : [{
'email' :
'[email protected] ' }],
594+ 'custom_args' : {'order_id' : '123' }},
595+ {
'to' : [{
'email' :
'[email protected] ' ,
'name' :
'"Bob"' }],
596+ 'custom_args' : {'order_id' : '678' , 'tier' : 'premium' }},
597+ ])
598+ self .assertEqual (data ['custom_args' ], {'anymail_id' : 'mocked-uuid-1' })
599+
600+ def test_metadata_with_merge_metadata (self ):
601+ # Per SendGrid docs: "personalizations[x].custom_args will be merged
602+ # with message level custom_args, overriding any conflicting keys."
603+ # So there's no need to merge global metadata with per-recipient merge_metadata
604+ # (like we have to for template merge_global_data and merge_data).
605+ 606+ self .message .metadata = {'tier' : 'basic' , 'batch' : 'ax24' }
607+ self .message .merge_metadata = {
608+ '[email protected] ' : {
'order_id' :
123 },
609+ '[email protected] ' : {
'order_id' :
678 ,
'tier' :
'premium' },
610+ }
611+ self .message .send ()
612+ data = self .get_api_call_json ()
613+ self .assertEqual (data ['personalizations' ], [
614+ {
'to' : [{
'email' :
'[email protected] ' }],
615+ 'custom_args' : {'order_id' : '123' }},
616+ {
'to' : [{
'email' :
'[email protected] ' ,
'name' :
'"Bob"' }],
617+ 'custom_args' : {'order_id' : '678' , 'tier' : 'premium' }},
618+ ])
619+ self .assertEqual (data ['custom_args' ],
620+ {'tier' : 'basic' , 'batch' : 'ax24' , 'anymail_id' : 'mocked-uuid-1' })
621+
622+ def test_merge_metadata_with_merge_data (self ):
623+ # (using dynamic templates)
624+ 625+ self .
message .
cc = [
'[email protected] ' ]
# gets applied to *each* recipient in a merge 626+ self .message .template_id = "d-5a963add2ec84305813ff860db277d7a"
627+ self .message .merge_data = {
628+ '[email protected] ' : {
'name' :
"Alice" ,
'group' :
"Developers" },
629+ '[email protected] ' : {
'name' :
"Bob" }
630+ # and no data for [email protected] 631+ }
632+ self .message .merge_global_data = {
633+ 'group' : "Users" ,
634+ 'site' : "ExampleCo" ,
635+ }
636+ self .message .merge_metadata = {
637+ '[email protected] ' : {
'order_id' :
123 },
638+ '[email protected] ' : {
'order_id' :
678 ,
'tier' :
'premium' },
639+ # and no metadata for [email protected] 640+ }
641+ self .message .send ()
642+ data = self .get_api_call_json ()
643+ self .assertEqual (data ['personalizations' ], [
644+ {
'to' : [{
'email' :
'[email protected] ' }],
645+ 'cc' : [{
'email' :
'[email protected] ' }],
# all recipients get the cc 646+ 'dynamic_template_data' : {
647+ 'name' : "Alice" , 'group' : "Developers" , 'site' : "ExampleCo" },
648+ 'custom_args' : {'order_id' : '123' }},
649+ {
'to' : [{
'email' :
'[email protected] ' ,
'name' :
'"Bob"' }],
650+ 'cc' : [{
'email' :
'[email protected] ' }],
651+ 'dynamic_template_data' : {
652+ 'name' : "Bob" , 'group' : "Users" , 'site' : "ExampleCo" },
653+ 'custom_args' : {'order_id' : '678' , 'tier' : 'premium' }},
654+ {
'to' : [{
'email' :
'[email protected] ' }],
655+ 'cc' : [{
'email' :
'[email protected] ' }],
656+ 'dynamic_template_data' : {
657+ 'group' : "Users" , 'site' : "ExampleCo" }},
658+ ])
659+
660+ def test_merge_metadata_with_legacy_template (self ):
661+ 662+ self .
message .
cc = [
'[email protected] ' ]
# gets applied to *each* recipient in a merge 663+ self .message .template_id = "5a963add2ec84305813ff860db277d7a"
664+ self .message .esp_extra = {'merge_field_format' : ':{}' }
665+ self .message .merge_data = {
666+ '[email protected] ' : {
'name' :
"Alice" ,
'group' :
"Developers" },
667+ '[email protected] ' : {
'name' :
"Bob" }
668+ # and no data for [email protected] 669+ }
670+ self .message .merge_global_data = {
671+ 'group' : "Users" ,
672+ 'site' : "ExampleCo" ,
673+ }
674+ self .message .merge_metadata = {
675+ '[email protected] ' : {
'order_id' :
123 },
676+ '[email protected] ' : {
'order_id' :
678 ,
'tier' :
'premium' },
677+ # and no metadata for [email protected] 678+ }
679+ self .message .send ()
680+ data = self .get_api_call_json ()
681+ self .assertEqual (data ['personalizations' ], [
682+ {
'to' : [{
'email' :
'[email protected] ' }],
683+ 'cc' : [{
'email' :
'[email protected] ' }],
# all recipients get the cc 684+ 'custom_args' : {'order_id' : '123' },
685+ 'substitutions' : {':name' : "Alice" , ':group' : "Developers" , ':site' : ":site" }},
686+ {
'to' : [{
'email' :
'[email protected] ' ,
'name' :
'"Bob"' }],
687+ 'cc' : [{
'email' :
'[email protected] ' }],
688+ 'custom_args' : {'order_id' : '678' , 'tier' : 'premium' },
689+ 'substitutions' : {':name' : "Bob" , ':group' : ":group" , ':site' : ":site" }},
690+ {
'to' : [{
'email' :
'[email protected] ' }],
691+ 'cc' : [{
'email' :
'[email protected] ' }],
692+ # no custom_args
693+ 'substitutions' : {':group' : ":group" , ':site' : ":site" }},
694+ ])
695+ self .assertEqual (data ['sections' ], {
696+ ':group' : "Users" ,
697+ ':site' : "ExampleCo" ,
698+ })
699+
576700 @override_settings (ANYMAIL_SENDGRID_GENERATE_MESSAGE_ID = False ) # else we force custom_args
577701 def test_default_omits_options (self ):
578702 """Make sure by default we don't send any ESP-specific options.
@@ -666,7 +790,7 @@ def test_send_attaches_anymail_status(self):
666790 sent = msg .send ()
667791 self .assertEqual (sent , 1 )
668792 self .assertEqual (msg .anymail_status .status , {'queued' })
669- self .assertUUIDIsValid (msg .anymail_status .message_id ) # don't know exactly what it'll be
793+ self .assertEqual (msg .anymail_status .message_id , 'mocked-uuid-1' )
670794 self .
assertEqual (
msg .
anymail_status .
recipients [
'[email protected] ' ].
status ,
'queued' )
671795 self .
assertEqual (
msg .
anymail_status .
recipients [
'[email protected] ' ].
message_id ,
672796 msg .anymail_status .message_id )
0 commit comments