4 * Class CRM_Utils_Mail_EmailProcessorTest
7 class CRM_Utils_Mail_EmailProcessorTest
extends CiviUnitTestCase
{
14 protected $eventQueue = [];
17 * ID of our sample contact.
23 public function setUp() {
25 CRM_Utils_File
::cleanDir(__DIR__
. '/data/mail');
26 mkdir(__DIR__
. '/data/mail');
27 $this->callAPISuccess('MailSettings', 'get', [
28 'api.MailSettings.create' => [
30 'protocol' => 'Localdir',
31 'source' => __DIR__
. '/data/mail',
32 'domain' => 'example.com',
37 public function tearDown() {
38 CRM_Utils_File
::cleanDir(__DIR__
. '/data/mail');
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']);
44 * Test the job processing function works and processes a bounce.
46 public function testBounceProcessing() {
47 $this->setUpMailing();
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'));
51 $this->callAPISuccess('job', 'fetch_bounces', []);
52 $this->assertFalse(file_exists(__DIR__
. '/data/mail/bounce_no_verp.txt'));
53 $this->checkMailingBounces(1);
57 * Test the job processing function can handle invalid characters.
59 public function testBounceProcessingInvalidCharacter() {
60 $this->setUpMailing();
61 $mail = 'test_invalid_character.eml';
63 copy(__DIR__
. '/data/bounces/' . $mail, __DIR__
. '/data/mail/' . $mail);
64 $this->callAPISuccess('job', 'fetch_bounces', []);
65 $this->assertFalse(file_exists(__DIR__
. '/data/mail/' . $mail));
66 $this->checkMailingBounces(1);
70 * Test that the job processing function can handle incoming utf8mb4 characters.
72 public function testBounceProcessingUTF8mb4() {
73 $this->setUpMailing();
74 $mail = 'test_utf8mb4_character.txt';
76 copy(__DIR__
. '/data/bounces/' . $mail, __DIR__
. '/data/mail/' . $mail);
77 $this->callAPISuccess('job', 'fetch_bounces', []);
78 $this->assertFalse(file_exists(__DIR__
. '/data/mail/' . $mail));
79 $this->checkMailingBounces(1);
83 * Tests that a multipart related email does not cause pain & misery & fatal errors.
85 * Sample taken from https://www.phpclasses.org/browse/file/14672.html
87 public function testProcessingMultipartRelatedEmail() {
88 $this->setUpMailing();
89 $mail = 'test_sample_message.eml';
91 copy(__DIR__
. '/data/bounces/' . $mail, __DIR__
. '/data/mail/' . $mail);
92 $this->callAPISuccess('job', 'fetch_bounces', []);
93 $this->assertFalse(file_exists(__DIR__
. '/data/mail/' . $mail));
94 $this->checkMailingBounces(1);
98 * Tests that a nested multipart email does not cause pain & misery & fatal errors.
100 * Sample anonymized from an email that broke bounce processing at Wikimedia
102 public function testProcessingNestedMultipartEmail() {
103 $this->setUpMailing();
104 $mail = 'test_nested_message.eml';
106 copy(__DIR__
. '/data/bounces/' . $mail, __DIR__
. '/data/mail/' . $mail);
107 $this->callAPISuccess('job', 'fetch_bounces', []);
108 $this->assertFalse(file_exists(__DIR__
. '/data/mail/' . $mail));
109 $this->checkMailingBounces(1);
113 * Test that a deleted email does not cause a hard fail.
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.
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.
125 * For not however, we are testing absence of mysql error in conjunction with CRM-20016.
127 public function testBounceProcessingDeletedEmail() {
128 $this->setUpMailing();
129 $this->callAPISuccess('Email', 'get', [
130 'contact_id' => $this->contactID
,
131 'api.email.delete' => 1,
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'));
136 $this->callAPISuccess('job', 'fetch_bounces', []);
137 $this->assertFalse(file_exists(__DIR__
. '/data/mail/bounce_no_verp.txt'));
138 $this->checkMailingBounces(1);
143 * Wrapper to check for mailing bounces.
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.
148 * @param int $expectedCount
150 public function checkMailingBounces($expectedCount) {
151 $this->assertEquals($expectedCount, CRM_Core_DAO
::singleValueQuery(
152 "SELECT count(*) FROM civicrm_mailing_event_bounce"
159 public function setUpMailing() {
160 $this->contactID
= $this->individualCreate(['email' => 'undeliverable@example.com']);
161 $groupID = $this->callAPISuccess('Group', 'create', [
162 'title' => 'Mailing group',
163 'api.GroupContact.create' => [
164 'contact_id' => $this->contactID
,
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']]);