Commit | Line | Data |
---|---|---|
cdc5c450 | 1 | <?php |
2 | ||
3 | /** | |
67991a1a | 4 | * Class CRM_Utils_Mail_EmailProcessorTest |
cdc5c450 | 5 | * @group headless |
6 | */ | |
67991a1a | 7 | class CRM_Utils_Mail_EmailProcessorTest extends CiviUnitTestCase { |
cdc5c450 | 8 | |
9 | /** | |
10 | * Event queue record. | |
11 | * | |
12 | * @var array | |
13 | */ | |
9099cab3 | 14 | protected $eventQueue = []; |
cdc5c450 | 15 | |
16 | /** | |
17 | * ID of our sample contact. | |
18 | * | |
19 | * @var int | |
20 | */ | |
21 | protected $contactID; | |
22 | ||
23 | public function setUp() { | |
24 | parent::setUp(); | |
e7693ddd | 25 | CRM_Utils_File::cleanDir(__DIR__ . '/data/mail'); |
26 | mkdir(__DIR__ . '/data/mail'); | |
9099cab3 CW |
27 | $this->callAPISuccess('MailSettings', 'get', [ |
28 | 'api.MailSettings.create' => [ | |
cdc5c450 | 29 | 'name' => 'local', |
30 | 'protocol' => 'Localdir', | |
31 | 'source' => __DIR__ . '/data/mail', | |
32 | 'domain' => 'example.com', | |
9099cab3 CW |
33 | ], |
34 | ]); | |
cdc5c450 | 35 | } |
36 | ||
e7693ddd | 37 | public function tearDown() { |
38 | CRM_Utils_File::cleanDir(__DIR__ . '/data/mail'); | |
39 | parent::tearDown(); | |
9099cab3 | 40 | $this->quickCleanup(['civicrm_group', 'civicrm_group_contact', 'civicrm_mailing', 'civicrm_mailing_job', 'civicrm_mailing_event_bounce', 'civicrm_mailing_event_queue', 'civicrm_mailing_group', 'civicrm_mailing_recipients', 'civicrm_contact', 'civicrm_email']); |
e7693ddd | 41 | } |
42 | ||
cdc5c450 | 43 | /** |
44 | * Test the job processing function works and processes a bounce. | |
45 | */ | |
46 | public function testBounceProcessing() { | |
47 | $this->setUpMailing(); | |
48 | ||
49 | copy(__DIR__ . '/data/bounces/bounce_no_verp.txt', __DIR__ . '/data/mail/bounce_no_verp.txt'); | |
50 | $this->assertTrue(file_exists(__DIR__ . '/data/mail/bounce_no_verp.txt')); | |
9099cab3 | 51 | $this->callAPISuccess('job', 'fetch_bounces', []); |
cdc5c450 | 52 | $this->assertFalse(file_exists(__DIR__ . '/data/mail/bounce_no_verp.txt')); |
53 | $this->checkMailingBounces(1); | |
54 | } | |
55 | ||
8c4991d2 TS |
56 | /** |
57 | * Test the job processing function can handle invalid characters. | |
58 | */ | |
59 | public function testBounceProcessingInvalidCharacter() { | |
60 | $this->setUpMailing(); | |
61 | $mail = 'test_invalid_character.eml'; | |
62 | ||
39b959db | 63 | copy(__DIR__ . '/data/bounces/' . $mail, __DIR__ . '/data/mail/' . $mail); |
9099cab3 | 64 | $this->callAPISuccess('job', 'fetch_bounces', []); |
8c4991d2 TS |
65 | $this->assertFalse(file_exists(__DIR__ . '/data/mail/' . $mail)); |
66 | $this->checkMailingBounces(1); | |
67 | } | |
68 | ||
ebe10b73 TS |
69 | /** |
70 | * Test that the job processing function can handle incoming utf8mb4 characters. | |
71 | */ | |
72 | public function testBounceProcessingUTF8mb4() { | |
73 | $this->setUpMailing(); | |
74 | $mail = 'test_utf8mb4_character.txt'; | |
75 | ||
39b959db | 76 | copy(__DIR__ . '/data/bounces/' . $mail, __DIR__ . '/data/mail/' . $mail); |
9099cab3 | 77 | $this->callAPISuccess('job', 'fetch_bounces', []); |
ebe10b73 TS |
78 | $this->assertFalse(file_exists(__DIR__ . '/data/mail/' . $mail)); |
79 | $this->checkMailingBounces(1); | |
80 | } | |
81 | ||
e7693ddd | 82 | /** |
83 | * Tests that a multipart related email does not cause pain & misery & fatal errors. | |
84 | * | |
85 | * Sample taken from https://www.phpclasses.org/browse/file/14672.html | |
86 | */ | |
87 | public function testProcessingMultipartRelatedEmail() { | |
88 | $this->setUpMailing(); | |
89 | $mail = 'test_sample_message.eml'; | |
90 | ||
39b959db | 91 | copy(__DIR__ . '/data/bounces/' . $mail, __DIR__ . '/data/mail/' . $mail); |
9099cab3 | 92 | $this->callAPISuccess('job', 'fetch_bounces', []); |
e7693ddd | 93 | $this->assertFalse(file_exists(__DIR__ . '/data/mail/' . $mail)); |
94 | $this->checkMailingBounces(1); | |
95 | } | |
96 | ||
01467aec EE |
97 | /** |
98 | * Tests that a nested multipart email does not cause pain & misery & fatal errors. | |
99 | * | |
100 | * Sample anonymized from an email that broke bounce processing at Wikimedia | |
101 | */ | |
102 | public function testProcessingNestedMultipartEmail() { | |
103 | $this->setUpMailing(); | |
104 | $mail = 'test_nested_message.eml'; | |
105 | ||
106 | copy(__DIR__ . '/data/bounces/' . $mail, __DIR__ . '/data/mail/' . $mail); | |
9099cab3 | 107 | $this->callAPISuccess('job', 'fetch_bounces', []); |
01467aec EE |
108 | $this->assertFalse(file_exists(__DIR__ . '/data/mail/' . $mail)); |
109 | $this->checkMailingBounces(1); | |
110 | } | |
111 | ||
cdc5c450 | 112 | /** |
113 | * Test that a deleted email does not cause a hard fail. | |
114 | * | |
115 | * The civicrm_mailing_event_queue table tracks email ids to represent an | |
116 | * email address. The id may not represent the same email by the time the bounce may | |
117 | * come in - a weakness of storing the id not the email. Relevant here | |
118 | * is that it might have been deleted altogether, in which case the bounce should be | |
119 | * silently ignored. This ignoring is also at the expense of the contact | |
120 | * having the same email address with a different id. | |
121 | * | |
122 | * Longer term it would make sense to track the email address & track bounces back to that | |
123 | * rather than an id that may not reflect the email used. Issue logged CRM-20021. | |
124 | * | |
125 | * For not however, we are testing absence of mysql error in conjunction with CRM-20016. | |
126 | */ | |
127 | public function testBounceProcessingDeletedEmail() { | |
128 | $this->setUpMailing(); | |
9099cab3 | 129 | $this->callAPISuccess('Email', 'get', [ |
cdc5c450 | 130 | 'contact_id' => $this->contactID, |
131 | 'api.email.delete' => 1, | |
9099cab3 | 132 | ]); |
cdc5c450 | 133 | |
134 | copy(__DIR__ . '/data/bounces/bounce_no_verp.txt', __DIR__ . '/data/mail/bounce_no_verp.txt'); | |
135 | $this->assertTrue(file_exists(__DIR__ . '/data/mail/bounce_no_verp.txt')); | |
9099cab3 | 136 | $this->callAPISuccess('job', 'fetch_bounces', []); |
cdc5c450 | 137 | $this->assertFalse(file_exists(__DIR__ . '/data/mail/bounce_no_verp.txt')); |
138 | $this->checkMailingBounces(1); | |
139 | } | |
140 | ||
141 | /** | |
e7693ddd | 142 | * |
cdc5c450 | 143 | * Wrapper to check for mailing bounces. |
144 | * | |
145 | * Normally we would call $this->callAPISuccessGetCount but there is not one & there is resistance to | |
146 | * adding apis for 'convenience' so just adding a hacky function to get past the impasse. | |
147 | * | |
148 | * @param int $expectedCount | |
149 | */ | |
150 | public function checkMailingBounces($expectedCount) { | |
151 | $this->assertEquals($expectedCount, CRM_Core_DAO::singleValueQuery( | |
e7693ddd | 152 | "SELECT count(*) FROM civicrm_mailing_event_bounce" |
cdc5c450 | 153 | )); |
154 | } | |
155 | ||
156 | /** | |
157 | * Set up a mailing. | |
158 | */ | |
159 | public function setUpMailing() { | |
9099cab3 CW |
160 | $this->contactID = $this->individualCreate(['email' => 'undeliverable@example.com']); |
161 | $groupID = $this->callAPISuccess('Group', 'create', [ | |
cdc5c450 | 162 | 'title' => 'Mailing group', |
9099cab3 | 163 | 'api.GroupContact.create' => [ |
cdc5c450 | 164 | 'contact_id' => $this->contactID, |
9099cab3 CW |
165 | ], |
166 | ]); | |
167 | $this->createMailing(['scheduled_date' => 'now', 'groups' => ['include' => [$groupID]]]); | |
168 | $this->callAPISuccess('job', 'process_mailing', []); | |
169 | $this->eventQueue = $this->callAPISuccess('MailingEventQueue', 'get', ['api.MailingEventQueue.create' => ['hash' => 'aaaaaaaaaaaaaaaa']]); | |
cdc5c450 | 170 | } |
171 | ||
172 | } |