Issues (3627)

EmailBundle/Tests/Model/SendEmailToContactTest.php (1 issue)

1
<?php
2
3
/*
4
 * @copyright   2017 Mautic Contributors. All rights reserved
5
 * @author      Mautic, Inc.
6
 *
7
 * @link        https://mautic.org
8
 *
9
 * @license     GNU/GPLv3 http://www.gnu.org/licenses/gpl-3.0.html
10
 */
11
12
namespace Mautic\EmailBundle\Tests\Model;
13
14
use Doctrine\ORM\EntityManager;
15
use Mautic\CoreBundle\Factory\MauticFactory;
16
use Mautic\CoreBundle\Translation\Translator;
17
use Mautic\EmailBundle\Entity\CopyRepository;
18
use Mautic\EmailBundle\Entity\Email;
19
use Mautic\EmailBundle\Entity\Stat;
20
use Mautic\EmailBundle\Entity\StatRepository;
21
use Mautic\EmailBundle\Event\EmailSendEvent;
22
use Mautic\EmailBundle\Exception\FailedToSendToContactException;
23
use Mautic\EmailBundle\Helper\MailHelper;
24
use Mautic\EmailBundle\Model\EmailModel;
25
use Mautic\EmailBundle\Model\SendEmailToContact;
26
use Mautic\EmailBundle\Stat\StatHelper;
27
use Mautic\EmailBundle\Swiftmailer\Exception\BatchQueueMaxException;
28
use Mautic\EmailBundle\Tests\Helper\Transport\BatchTransport;
29
use Mautic\LeadBundle\Entity\Lead;
30
use Mautic\LeadBundle\Model\DoNotContact;
31
use Psr\Log\NullLogger;
32
use Symfony\Component\EventDispatcher\EventDispatcher;
33
use Symfony\Component\Routing\Router;
34
35
class SendEmailToContactTest extends \PHPUnit\Framework\TestCase
36
{
37
    protected $contacts = [
38
        [
39
            'id'        => 1,
40
            'email'     => '[email protected]',
41
            'firstname' => 'Contact',
42
            'lastname'  => '1',
43
            'owner_id'  => 1,
44
        ],
45
        [
46
            'id'        => 2,
47
            'email'     => '[email protected]',
48
            'firstname' => 'Contact',
49
            'lastname'  => '2',
50
            'owner_id'  => 0,
51
        ],
52
        [
53
            'id'        => 3,
54
            'email'     => '[email protected]',
55
            'firstname' => 'Contact',
56
            'lastname'  => '3',
57
            'owner_id'  => 2,
58
        ],
59
        [
60
            'id'        => 4,
61
            'email'     => '[email protected]',
62
            'firstname' => 'Contact',
63
            'lastname'  => '4',
64
            'owner_id'  => 1,
65
        ],
66
    ];
67
68
    /**
69
     * @testdox Tests that all contacts are temporarily failed if an Email entity happens to be incorrectly configured
70
     *
71
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setEmail()
72
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
73
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
74
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::finalFlush()
75
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
76
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getFailedContacts()
77
     */
78
    public function testContactsAreFailedIfSettingEmailEntityFails()
79
    {
80
        $mailHelper = $this->getMockBuilder(MailHelper::class)
81
            ->disableOriginalConstructor()
82
            ->getMock();
83
        $mailHelper->method('setEmail')
84
            ->willReturn(false);
85
86
        $statRepository = $this->getMockBuilder(StatRepository::class)
87
            ->disableOriginalConstructor()
88
            ->getMock();
89
90
        $dncModel = $this->getMockBuilder(DoNotContact::class)
91
            ->disableOriginalConstructor()
92
            ->getMock();
93
94
        // This should not be called because contact emails are just fine; the problem is with the email entity
95
        $dncModel->expects($this->never())
96
            ->method('addDncForContact');
97
98
        $translator = $this->getMockBuilder(Translator::class)
99
            ->disableOriginalConstructor()
100
            ->getMock();
101
102
        $statHelper = new StatHelper($statRepository);
103
104
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
105
106
        $email = new Email();
107
        $model->setEmail($email);
108
109
        foreach ($this->contacts as $contact) {
110
            try {
111
                $model->setContact($contact)
112
                    ->send();
113
            } catch (FailedToSendToContactException $exception) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
114
            }
115
        }
116
117
        $model->finalFlush();
118
119
        $failedContacts = $model->getFailedContacts();
120
121
        $this->assertCount(4, $failedContacts);
122
    }
123
124
    /**
125
     * @testdox Tests that bad emails are failed
126
     *
127
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
128
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
129
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::finalFlush()
130
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
131
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getFailedContacts()
132
     */
133
    public function testExceptionIsThrownIfEmailIsSentToBadContact()
134
    {
135
        $emailMock = $this->getMockBuilder(Email::class)
136
            ->getMock();
137
        $emailMock
138
            ->expects($this->any())
139
            ->method('getId')
140
            ->will($this->returnValue(1));
141
142
        $mailHelper = $this->getMockBuilder(MailHelper::class)
143
            ->disableOriginalConstructor()
144
            ->getMock();
145
        $mailHelper->method('setEmail')
146
            ->willReturn(true);
147
        $mailHelper->method('addTo')
148
            ->willReturnCallback(
149
                function ($email) {
150
                    return '@bad.com' !== $email;
151
                }
152
            );
153
        $mailHelper->method('queue')
154
            ->willReturn([true, []]);
155
156
        $stat = new Stat();
157
        $stat->setEmail($emailMock);
158
        $mailHelper->method('createEmailStat')
159
            ->willReturn($stat);
160
161
        $statRepository = $this->getMockBuilder(StatRepository::class)
162
            ->disableOriginalConstructor()
163
            ->getMock();
164
165
        $dncModel = $this->getMockBuilder(DoNotContact::class)
166
            ->disableOriginalConstructor()
167
            ->getMock();
168
169
        $dncModel->expects($this->once())
170
            ->method('addDncForContact');
171
172
        $translator = $this->getMockBuilder(Translator::class)
173
            ->disableOriginalConstructor()
174
            ->getMock();
175
176
        $statHelper = new StatHelper($statRepository);
177
178
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
179
        $model->setEmail($emailMock);
180
181
        $contacts             = $this->contacts;
182
        $contacts[0]['email'] = '@bad.com';
183
184
        $exceptionThrown = false;
185
        foreach ($contacts as $contact) {
186
            try {
187
                $model->setContact($contact)
188
                    ->send();
189
            } catch (FailedToSendToContactException $exception) {
190
                $exceptionThrown = true;
191
            }
192
        }
193
194
        if (!$exceptionThrown) {
195
            $this->fail('FailedToSendToContactException not thrown');
196
        }
197
198
        $model->finalFlush();
199
200
        $failedContacts = $model->getFailedContacts();
201
202
        $this->assertCount(1, $failedContacts);
203
    }
204
205
    /**
206
     * @testdox Test a tokenized transport that limits batches does not throw BatchQueueMaxException on subsequent contacts when one fails
207
     *
208
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
209
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
210
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
211
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getFailedContacts()
212
     */
213
    public function testBadEmailDoesNotCauseBatchQueueMaxExceptionOnSubsequentContacts()
214
    {
215
        defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
216
217
        $emailMock = $this->getMockBuilder(Email::class)
218
            ->getMock();
219
        $emailMock
220
            ->method('getId')
221
            ->will($this->returnValue(1));
222
        $emailMock->method('getFromAddress')
223
            ->willReturn('[email protected]');
224
225
        // Use our test token transport limiting to 1 recipient per queue
226
        $transport = new BatchTransport(false, 1);
227
        $mailer    = new \Swift_Mailer($transport);
228
229
        // Mock factory to ensure that queue mode is handled until MailHelper is refactored completely away from MauticFactory
230
        $factoryMock = $this->getMockBuilder(MauticFactory::class)
231
            ->disableOriginalConstructor()
232
            ->getMock();
233
        $factoryMock->method('getParameter')
234
            ->willReturnCallback(
235
                function ($param) {
236
                    switch ($param) {
237
                        case 'mailer_spool_type':
238
                            return 'memory';
239
                        default:
240
                            return '';
241
                    }
242
                }
243
            );
244
        $factoryMock->method('getLogger')
245
            ->willReturn(
246
                new NullLogger()
247
            );
248
        $factoryMock->method('getDispatcher')
249
            ->willReturn(
250
                new EventDispatcher()
251
            );
252
        $routerMock = $this->getMockBuilder(Router::class)
253
            ->disableOriginalConstructor()
254
            ->getMock();
255
        $factoryMock->method('getRouter')
256
            ->willReturn($routerMock);
257
258
        $mailHelper = $this->getMockBuilder(MailHelper::class)
259
            ->setConstructorArgs([$factoryMock, $mailer])
260
            ->setMethods(['createEmailStat'])
261
            ->getMock();
262
263
        $mailHelper->method('createEmailStat')
264
            ->willReturnCallback(
265
                function () use ($emailMock) {
266
                    $stat = new Stat();
267
                    $stat->setEmail($emailMock);
268
269
                    $leadMock = $this->getMockBuilder(Lead::class)
270
                        ->getMock();
271
                    $leadMock->method('getId')
272
                        ->willReturn(1);
273
274
                    $stat->setLead($leadMock);
275
276
                    return $stat;
277
                }
278
            );
279
280
        // Enable queueing
281
        $mailHelper->enableQueue();
282
283
        $statRepository = $this->getMockBuilder(StatRepository::class)
284
            ->disableOriginalConstructor()
285
            ->getMock();
286
287
        $dncModel = $this->getMockBuilder(DoNotContact::class)
288
            ->disableOriginalConstructor()
289
            ->getMock();
290
291
        $dncModel->expects($this->exactly(1))
292
            ->method('addDncForContact');
293
294
        $translator = $this->getMockBuilder(Translator::class)
295
            ->disableOriginalConstructor()
296
            ->getMock();
297
298
        $statHelper = new StatHelper($statRepository);
299
300
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
301
        $model->setEmail($emailMock);
302
303
        $contacts             = $this->contacts;
304
        $contacts[0]['email'] = '@bad.com';
305
306
        foreach ($contacts as $contact) {
307
            try {
308
                $model->setContact($contact)
309
                    ->send();
310
            } catch (FailedToSendToContactException $exception) {
311
                // We're good here
312
            } catch (BatchQueueMaxException $exception) {
313
                $this->fail('BatchQueueMaxException thrown');
314
            }
315
        }
316
317
        $model->finalFlush();
318
319
        $failedContacts = $model->getFailedContacts();
320
321
        $this->assertCount(1, $failedContacts);
322
323
        // Our fake transport should have processed 3 metadatas
324
        $this->assertCount(3, $transport->getMetadatas());
325
326
        // We made it this far so all of the emails were processed despite a bad email in the batch
327
    }
328
329
    /**
330
     * @testdox Test a tokenized transport that fills tokens correctly
331
     *
332
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
333
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
334
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
335
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getFailedContacts()
336
     */
337
    public function testBatchQueueContactsHaveTokensHydrated()
338
    {
339
        defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
340
341
        $emailMock = $this->getMockBuilder(Email::class)
342
            ->getMock();
343
        $emailMock
344
            ->method('getId')
345
            ->will($this->returnValue(1));
346
        $emailMock->method('getFromAddress')
347
            ->willReturn('[email protected]');
348
        $emailMock->method('getCustomHtml')
349
            ->willReturn('Hi {contactfield=firstname}');
350
351
        // Use our test token transport limiting to 1 recipient per queue
352
        $transport = new BatchTransport(false, 1);
353
        $mailer    = new \Swift_Mailer($transport);
354
355
        // Mock factory to ensure that queue mode is handled until MailHelper is refactored completely away from MauticFactory
356
        $factoryMock = $this->getMockBuilder(MauticFactory::class)
357
            ->disableOriginalConstructor()
358
            ->getMock();
359
        $factoryMock->method('getParameter')
360
            ->willReturnCallback(
361
                function ($param) {
362
                    switch ($param) {
363
                        case 'mailer_spool_type':
364
                            return 'memory';
365
                        default:
366
                            return '';
367
                    }
368
                }
369
            );
370
        $factoryMock->method('getLogger')
371
            ->willReturn(
372
                new NullLogger()
373
            );
374
375
        $mockEm = $this->getMockBuilder(EntityManager::class)
376
            ->disableOriginalConstructor()
377
            ->getMock();
378
        $factoryMock->method('getEntityManager')
379
            ->willReturn($mockEm);
380
381
        $mockDispatcher = $this->getMockBuilder(EventDispatcher::class)
382
            ->getMock();
383
        $mockDispatcher->method('dispatch')
384
            ->willReturnCallback(
385
                function ($eventName, EmailSendEvent $event) {
386
                    $lead = $event->getLead();
387
388
                    $tokens = [];
389
                    foreach ($lead as $field => $value) {
390
                        $tokens["{contactfield=$field}"] = $value;
391
                    }
392
                    $tokens['{hash}'] = $event->getIdHash();
393
394
                    $event->addTokens($tokens);
395
                }
396
            );
397
        $factoryMock->method('getDispatcher')
398
            ->willReturn($mockDispatcher);
399
        $routerMock = $this->getMockBuilder(Router::class)
400
            ->disableOriginalConstructor()
401
            ->getMock();
402
        $factoryMock->method('getRouter')
403
            ->willReturn($routerMock);
404
405
        $copyRepoMock = $this->getMockBuilder(CopyRepository::class)
406
            ->disableOriginalConstructor()
407
            ->getMock();
408
        $emailModelMock = $this->getMockBuilder(EmailModel::class)
409
            ->disableOriginalConstructor()
410
            ->getMock();
411
        $emailModelMock->method('getCopyRepository')
412
            ->willReturn($copyRepoMock);
413
414
        $factoryMock->method('getModel')
415
            ->willReturn($emailModelMock);
416
417
        $mailHelper = $this->getMockBuilder(MailHelper::class)
418
            ->setConstructorArgs([$factoryMock, $mailer])
419
            ->setMethods(null)
420
            ->getMock();
421
422
        // Enable queueing
423
        $mailHelper->enableQueue();
424
425
        $statRepository = $this->getMockBuilder(StatRepository::class)
426
            ->disableOriginalConstructor()
427
            ->getMock();
428
        $statRepository->method('saveEntity')
429
            ->willReturnCallback(
430
                function (Stat $stat) {
431
                    $tokens = $stat->getTokens();
432
                    $this->assertGreaterThan(1, count($tokens));
433
                    $this->assertEquals($stat->getTrackingHash(), $tokens['{hash}']);
434
                }
435
            );
436
437
        $dncModel = $this->getMockBuilder(DoNotContact::class)
438
            ->disableOriginalConstructor()
439
            ->getMock();
440
441
        $translator = $this->getMockBuilder(Translator::class)
442
            ->disableOriginalConstructor()
443
            ->getMock();
444
445
        $statHelper = new StatHelper($statRepository);
446
447
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
448
        $model->setEmail($emailMock);
449
450
        foreach ($this->contacts as $contact) {
451
            try {
452
                $model->setContact($contact)
453
                    ->send();
454
            } catch (FailedToSendToContactException $exception) {
455
                // We're good here
456
            } catch (BatchQueueMaxException $exception) {
457
                $this->fail('BatchQueueMaxException thrown');
458
            }
459
        }
460
461
        $model->finalFlush();
462
463
        $this->assertCount(4, $transport->getMetadatas());
464
    }
465
466
    /**
467
     * @testdox Test that stat entries are saved in batches of 20
468
     *
469
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
470
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
471
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
472
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::createContactStatEntry()
473
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getFailedContacts()
474
     */
475
    public function testThatStatEntriesAreCreatedAndPersistedEveryBatch()
476
    {
477
        defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
478
479
        $emailMock = $this->getMockBuilder(Email::class)
480
            ->getMock();
481
        $emailMock
482
            ->method('getId')
483
            ->will($this->returnValue(1));
484
        $emailMock->method('getFromAddress')
485
            ->willReturn('[email protected]');
486
487
        // Use our test token transport limiting to 1 recipient per queue
488
        $transport = new BatchTransport(false, 1);
489
        $mailer    = new \Swift_Mailer($transport);
490
491
        // Mock factory to ensure that queue mode is handled until MailHelper is refactored completely away from MauticFactory
492
        $factoryMock = $this->getMockBuilder(MauticFactory::class)
493
            ->disableOriginalConstructor()
494
            ->getMock();
495
        $factoryMock->method('getParameter')
496
            ->willReturnCallback(
497
                function ($param) {
498
                    switch ($param) {
499
                        case 'mailer_spool_type':
500
                            return 'memory';
501
                        default:
502
                            return '';
503
                    }
504
                }
505
            );
506
        $factoryMock->method('getLogger')
507
            ->willReturn(
508
                new NullLogger()
509
            );
510
        $factoryMock->method('getDispatcher')
511
            ->willReturn(
512
                new EventDispatcher()
513
            );
514
        $routerMock = $this->getMockBuilder(Router::class)
515
            ->disableOriginalConstructor()
516
            ->getMock();
517
        $factoryMock->method('getRouter')
518
            ->willReturn($routerMock);
519
520
        $mailHelper = $this->getMockBuilder(MailHelper::class)
521
            ->setConstructorArgs([$factoryMock, $mailer])
522
            ->setMethods(['createEmailStat'])
523
            ->getMock();
524
525
        $mailHelper->expects($this->exactly(21))
526
            ->method('createEmailStat')
527
            ->willReturnCallback(
528
                function () use ($emailMock) {
529
                    $stat = new Stat();
530
                    $stat->setEmail($emailMock);
531
532
                    $leadMock = $this->getMockBuilder(Lead::class)
533
                        ->getMock();
534
                    $leadMock->method('getId')
535
                        ->willReturn(1);
536
537
                    $stat->setLead($leadMock);
538
539
                    return $stat;
540
                }
541
            );
542
543
        // Enable queueing
544
        $mailHelper->enableQueue();
545
546
        // Here's the test; this should be called after 20 contacts are processed
547
        $statRepository = $this->getMockBuilder(StatRepository::class)
548
            ->disableOriginalConstructor()
549
            ->getMock();
550
        $statRepository->expects($this->exactly(21))
551
            ->method('saveEntity');
552
553
        $dncModel = $this->getMockBuilder(DoNotContact::class)
554
            ->disableOriginalConstructor()
555
            ->getMock();
556
557
        $dncModel->expects($this->never())
558
            ->method('addDncForContact');
559
560
        $translator = $this->getMockBuilder(Translator::class)
561
            ->disableOriginalConstructor()
562
            ->getMock();
563
564
        $statHelper = new StatHelper($statRepository);
565
566
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
567
        $model->setEmail($emailMock);
568
569
        // Let's generate 20 bogus contacts
570
        $contacts = [];
571
        $counter  = 0;
572
        while ($counter <= 20) {
573
            $contacts[] = [
574
                'id'        => $counter,
575
                'email'     => 'email'.uniqid().'@somewhere.com',
576
                'firstname' => 'Contact',
577
                'lastname'  => uniqid(),
578
            ];
579
580
            ++$counter;
581
        }
582
583
        foreach ($contacts as $contact) {
584
            try {
585
                $model->setContact($contact)
586
                    ->send();
587
            } catch (FailedToSendToContactException $exception) {
588
                $this->fail('FailedToSendToContactException thrown: '.$exception->getMessage());
589
            } catch (BatchQueueMaxException $exception) {
590
                $this->fail('BatchQueueMaxException thrown: '.$exception->getMessage());
591
            }
592
        }
593
594
        $model->finalFlush();
595
596
        $failedContacts = $model->getFailedContacts();
597
        $this->assertCount(0, $failedContacts);
598
        $this->assertCount(21, $transport->getMetadatas());
599
    }
600
601
    /**
602
     * @testdox Test that a failed email from the transport is handled
603
     *
604
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
605
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
606
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
607
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getFailedContacts()
608
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::upEmailSentCount()
609
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::downEmailSentCount()
610
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::getSentCounts()
611
     */
612
    public function testThatAFailureFromTransportIsHandled()
613
    {
614
        defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
615
616
        $emailMock = $this->getMockBuilder(Email::class)
617
            ->getMock();
618
        $emailMock
619
            ->method('getId')
620
            ->will($this->returnValue(1));
621
        $emailMock->method('getFromAddress')
622
            ->willReturn('[email protected]');
623
624
        // Use our test token transport limiting to 1 recipient per queue
625
        $transport = new BatchTransport(true, 1);
626
        $mailer    = new \Swift_Mailer($transport);
627
628
        // Mock factory to ensure that queue mode is handled until MailHelper is refactored completely away from MauticFactory
629
        $factoryMock = $this->getMockBuilder(MauticFactory::class)
630
            ->disableOriginalConstructor()
631
            ->getMock();
632
        $factoryMock->method('getParameter')
633
            ->willReturnCallback(
634
                function ($param) {
635
                    switch ($param) {
636
                        case 'mailer_spool_type':
637
                            return 'memory';
638
                        default:
639
                            return '';
640
                    }
641
                }
642
            );
643
        $factoryMock->method('getLogger')
644
            ->willReturn(
645
                new NullLogger()
646
            );
647
        $factoryMock->method('getDispatcher')
648
            ->willReturn(
649
                new EventDispatcher()
650
            );
651
        $routerMock = $this->getMockBuilder(Router::class)
652
            ->disableOriginalConstructor()
653
            ->getMock();
654
        $factoryMock->method('getRouter')
655
            ->willReturn($routerMock);
656
657
        $mailHelper = $this->getMockBuilder(MailHelper::class)
658
            ->setConstructorArgs([$factoryMock, $mailer])
659
            ->setMethods(['createEmailStat'])
660
            ->getMock();
661
662
        $mailHelper->method('createEmailStat')
663
            ->willReturnCallback(
664
                function () use ($emailMock) {
665
                    $stat = new Stat();
666
                    $stat->setEmail($emailMock);
667
668
                    $leadMock = $this->getMockBuilder(Lead::class)
669
                        ->getMock();
670
                    $leadMock->method('getId')
671
                        ->willReturn(1);
672
673
                    $stat->setLead($leadMock);
674
675
                    return $stat;
676
                }
677
            );
678
679
        // Enable queueing
680
        $mailHelper->enableQueue();
681
682
        $statRepository = $this->getMockBuilder(StatRepository::class)
683
            ->disableOriginalConstructor()
684
            ->getMock();
685
686
        $dncModel = $this->getMockBuilder(DoNotContact::class)
687
            ->disableOriginalConstructor()
688
            ->getMock();
689
690
        $dncModel->expects($this->never())
691
            ->method('addDncForContact');
692
693
        $translator = $this->getMockBuilder(Translator::class)
694
            ->disableOriginalConstructor()
695
            ->getMock();
696
697
        $statHelper = new StatHelper($statRepository);
698
699
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
700
        $model->setEmail($emailMock);
701
702
        foreach ($this->contacts as $contact) {
703
            try {
704
                $model->setContact($contact)
705
                    ->send();
706
            } catch (FailedToSendToContactException $exception) {
707
                // We're good here
708
            } catch (BatchQueueMaxException $exception) {
709
                $this->fail('BatchQueueMaxException thrown');
710
            }
711
        }
712
713
        $model->finalFlush();
714
715
        $failedContacts = $model->getFailedContacts();
716
717
        $this->assertCount(1, $failedContacts);
718
719
        $counts = $model->getSentCounts();
720
721
        // Should have increased to 4, one failed via the transport so back down to 3
722
        $this->assertEquals(3, $counts[1]);
723
724
        // One error message from the transport
725
        $errorMessages = $model->getErrors();
726
        $this->assertCount(1, $errorMessages);
727
    }
728
729
    /**
730
     * @testdox Test that sending an email with invalid Bcc address is handled
731
     *
732
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::setContact()
733
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::send()
734
     * @covers \Mautic\EmailBundle\Model\SendEmailToContact::failContact()
735
     */
736
    public function testThatInvalidBccFailureIsHandled()
737
    {
738
        defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
739
740
        $mockFactory = $this->getMockBuilder(MauticFactory::class)
741
            ->disableOriginalConstructor()
742
            ->getMock();
743
        $mockFactory->method('getParameter')
744
            ->will(
745
                $this->returnValueMap(
746
                    [
747
                        ['mailer_return_path', false, null],
748
                        ['mailer_spool_type', false, 'memory'],
749
                    ]
750
                )
751
            );
752
        $mockFactory->method('getLogger')
753
            ->willReturn(
754
                new NullLogger()
755
            );
756
757
        $swiftMailer = new \Swift_Mailer(new BatchTransport());
758
759
        $mailHelper = new MailHelper($mockFactory, $swiftMailer, ['[email protected]' => 'No Body']);
760
761
        $statRepository = $this->getMockBuilder(StatRepository::class)
762
            ->disableOriginalConstructor()
763
            ->getMock();
764
765
        $dncModel = $this->getMockBuilder(DoNotContact::class)
766
            ->disableOriginalConstructor()
767
            ->getMock();
768
769
        $translator = $this->getMockBuilder(Translator::class)
770
            ->disableOriginalConstructor()
771
            ->getMock();
772
773
        $statHelper = new StatHelper($statRepository);
774
775
        $model = new SendEmailToContact($mailHelper, $statHelper, $dncModel, $translator);
776
777
        $emailMock = $this->getMockBuilder(Email::class)
778
            ->getMock();
779
        $emailMock
780
            ->expects($this->any())
781
            ->method('getId')
782
            ->will($this->returnValue(1));
783
784
        // Set invalid BCC (should use comma as separator)
785
        $emailMock
786
            ->expects($this->any())
787
            ->method('getBccAddress')
788
            ->willReturn('[email protected]; [email protected]');
789
790
        $model->setEmail($emailMock);
791
792
        $stat = new Stat();
793
        $stat->setEmail($emailMock);
794
795
        $this->expectException(FailedToSendToContactException::class);
796
        $this->expectExceptionMessage('Address in mailbox given [[email protected]; [email protected]] does not comply with RFC 2822, 3.6.2.');
797
798
        // Send should trigger the FailedToSendToContactException
799
        $model->setContact($this->contacts[0])->send();
800
    }
801
}
802