Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Mautic\CategoryBundle\Entity\Category;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\EmailBundle\Entity\Email;
|
||||
use Mautic\EmailBundle\Entity\EmailRepository;
|
||||
use Mautic\EmailBundle\Model\EmailActionModel;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class EmailActionModelTest extends TestCase
|
||||
{
|
||||
public const NEW_CATEGORY_TITLE = 'New category';
|
||||
public const OLD_CATEGORY_TITLE = 'Old category';
|
||||
|
||||
/**
|
||||
* @var MockObject&EmailModel
|
||||
*/
|
||||
private MockObject $emailModelMock;
|
||||
|
||||
/**
|
||||
* @var MockObject&EmailRepository
|
||||
*/
|
||||
private MockObject $emailRepositoryMock;
|
||||
|
||||
/**
|
||||
* @var MockObject&CorePermissions
|
||||
*/
|
||||
private MockObject $corePermissionsMock;
|
||||
|
||||
private EmailActionModel $emailActionModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->emailModelMock = $this->createMock(EmailModel::class);
|
||||
$this->emailRepositoryMock = $this->createMock(EmailRepository::class);
|
||||
$this->corePermissionsMock = $this->createMock(CorePermissions::class);
|
||||
$this->emailActionModel = new EmailActionModel(
|
||||
$this->emailModelMock,
|
||||
$this->emailRepositoryMock,
|
||||
$this->corePermissionsMock
|
||||
);
|
||||
}
|
||||
|
||||
public function testSetsNewCategoryForEditableEmails(): void
|
||||
{
|
||||
$oldCategory = new Category();
|
||||
$oldCategory->setTitle(self::OLD_CATEGORY_TITLE);
|
||||
|
||||
$newCategory = new Category();
|
||||
$newCategory->setTitle(self::NEW_CATEGORY_TITLE);
|
||||
|
||||
$emails = $this->buildEmailsWithCategory($oldCategory, 3);
|
||||
$this->configureRepositoryToReturn($emails);
|
||||
$this->configurePermissionToAllowEdition(true);
|
||||
$this->configureModelToSave($emails);
|
||||
|
||||
$this->tryToSetCategory($emails, $newCategory);
|
||||
|
||||
foreach ($emails as $email) {
|
||||
$this->assertEquals($email->getCategory(), $newCategory);
|
||||
}
|
||||
}
|
||||
|
||||
public function testDoesntSetNewCategoryForNonEditableEmails(): void
|
||||
{
|
||||
$oldCategory = new Category();
|
||||
$oldCategory->setTitle(self::OLD_CATEGORY_TITLE);
|
||||
|
||||
$newCategory = new Category();
|
||||
$newCategory->setTitle(self::NEW_CATEGORY_TITLE);
|
||||
|
||||
$emails = $this->buildEmailsWithCategory($oldCategory, 5);
|
||||
$this->configureRepositoryToReturn($emails);
|
||||
$this->configurePermissionToAllowEdition(false);
|
||||
$this->tryToSetCategory($emails, $newCategory);
|
||||
|
||||
foreach ($emails as $email) {
|
||||
$this->assertEquals($email->getCategory(), $oldCategory);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<Email>
|
||||
*/
|
||||
private function buildEmailsWithCategory(Category $category, int $quantity): array
|
||||
{
|
||||
$emails = [];
|
||||
|
||||
for ($i = 0; $i < $quantity; ++$i) {
|
||||
$email = new Email();
|
||||
$email->setId($i);
|
||||
$email->setCategory($category);
|
||||
$emails[] = $email;
|
||||
}
|
||||
|
||||
return $emails;
|
||||
}
|
||||
|
||||
private function configurePermissionToAllowEdition(bool $allow): void
|
||||
{
|
||||
$this->corePermissionsMock
|
||||
->method('hasEntityAccess')
|
||||
->willReturn($allow);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Email> $emails
|
||||
*/
|
||||
protected function configureRepositoryToReturn(array $emails): void
|
||||
{
|
||||
$this->emailRepositoryMock
|
||||
->method('findBy')
|
||||
->with(
|
||||
['id' => array_map(fn (Email $email) => $email->getId(), $emails)]
|
||||
)
|
||||
->willReturn($emails);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Email> $emails
|
||||
*/
|
||||
protected function configureModelToSave(array $emails): void
|
||||
{
|
||||
$this->emailModelMock
|
||||
->expects($this->once())
|
||||
->method('saveEntities')
|
||||
->with($emails);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<Email> $emails
|
||||
*/
|
||||
protected function tryToSetCategory(array $emails, Category $newCategory): void
|
||||
{
|
||||
$this->emailActionModel
|
||||
->setCategory(
|
||||
array_map(fn (Email $email) => $email->getId(), $emails),
|
||||
$newCategory
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
|
||||
class EmailModelBuildUrlTest extends MauticMysqlTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->configParams['site_url'] = 'https://foo.bar.com';
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public function testSiteUrlAlwaysTakesPrecedenceWhenBuildingUrls(): void
|
||||
{
|
||||
/** @var EmailModel $emailModel */
|
||||
$emailModel = static::getContainer()->get('mautic.email.model.email');
|
||||
$idHash = uniqid();
|
||||
$url = $emailModel->buildUrl('mautic_email_unsubscribe', ['idHash' => $idHash]);
|
||||
|
||||
self::assertSame('https://foo.bar.com/email/unsubscribe/'.$idHash, $url);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Doctrine\DBAL\Exception;
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Doctrine\ORM\OptimisticLockException;
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\CoreBundle\Tests\Functional\CreateTestEntitiesTrait;
|
||||
use Mautic\EmailBundle\Entity\Email;
|
||||
use Mautic\EmailBundle\Entity\Stat;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadList;
|
||||
use Mautic\LeadBundle\Entity\ListLead;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\PageBundle\Entity\Hit;
|
||||
use Mautic\PageBundle\Entity\Redirect;
|
||||
use Mautic\PageBundle\Entity\Trackable;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
class EmailModelFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
use CreateTestEntitiesTrait;
|
||||
|
||||
private EmailModel|ContainerInterface $emailModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->emailModel = static::getContainer()->get('mautic.email.model.email');
|
||||
}
|
||||
|
||||
protected function beforeBeginTransaction(): void
|
||||
{
|
||||
$this->resetAutoincrement(['leads']);
|
||||
}
|
||||
|
||||
public function testSendEmailToListsInThreads(): void
|
||||
{
|
||||
$contacts = $this->generateContacts(23);
|
||||
$segment = $this->createSegment();
|
||||
$this->addContactsToSegment($contacts, $segment);
|
||||
$email = $this->createEmail($segment);
|
||||
|
||||
$emailModel = static::getContainer()->get('mautic.email.model.email');
|
||||
\assert($emailModel instanceof EmailModel);
|
||||
[$sentCount] = $this->emailModel->sendEmailToLists($email, [$segment], null, null, null, null, null, 3, 1);
|
||||
$this->assertEquals($sentCount, 7);
|
||||
[$sentCount] = $this->emailModel->sendEmailToLists($email, [$segment], null, null, null, null, null, 3, 2);
|
||||
$this->assertEquals($sentCount, 8);
|
||||
[$sentCount] = $this->emailModel->sendEmailToLists($email, [$segment], null, null, null, null, null, 3, 3);
|
||||
$this->assertEquals($sentCount, 8);
|
||||
}
|
||||
|
||||
public function testGetEmailGeneralStats(): void
|
||||
{
|
||||
$contacts = $this->generateContacts(12);
|
||||
$segment = $this->createSegment();
|
||||
$this->addContactsToSegment($contacts, $segment);
|
||||
$email = $this->createEmail($segment);
|
||||
|
||||
// Send email to segment
|
||||
[$sentCount] = $this->emailModel->sendEmailToLists($email, [$segment]);
|
||||
|
||||
// Emulate email reads
|
||||
$statRepository = $this->em->getRepository(Stat::class);
|
||||
$stats = $statRepository->findBy([
|
||||
'email' => $email,
|
||||
'lead' => $contacts,
|
||||
]);
|
||||
for ($index = 0; $index < $readCount = 4; ++$index) {
|
||||
$this->emulateEmailRead($stats[$index]);
|
||||
}
|
||||
|
||||
// Emulate clicks
|
||||
$this->emulateClick($contacts[0], $email, 1, 1);
|
||||
$this->emulateClick($contacts[1], $email, 1, 1);
|
||||
|
||||
// Emulate unsubscribing and bounces
|
||||
$this->createDnc('email', $contacts[3], DoNotContact::UNSUBSCRIBED, $email->getId());
|
||||
$this->createDnc('email', $contacts[4], DoNotContact::BOUNCED, $email->getId());
|
||||
|
||||
// Emulate failed email
|
||||
$this->emulateEmailFailed($stats[5]);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$dateFrom = new \DateTime('-7 days');
|
||||
$dateTo = new \DateTime();
|
||||
$unit = 'D';
|
||||
$includeVariants = false;
|
||||
|
||||
$result = $this->emailModel->getEmailGeneralStats($email, $includeVariants, $unit, $dateFrom, $dateTo);
|
||||
|
||||
$this->assertIsArray($result);
|
||||
$this->assertCount(6, $result['datasets']);
|
||||
$this->assertEquals('Sent emails', $result['datasets'][0]['label']);
|
||||
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, $sentCount], $result['datasets'][0]['data']);
|
||||
$this->assertEquals('Read emails', $result['datasets'][1]['label']);
|
||||
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, $readCount], $result['datasets'][1]['data']);
|
||||
$this->assertEquals('Failed emails', $result['datasets'][2]['label']);
|
||||
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, 1], $result['datasets'][2]['data']);
|
||||
$this->assertEquals('Unique Clicked', $result['datasets'][3]['label']);
|
||||
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, 2], $result['datasets'][3]['data']);
|
||||
$this->assertEquals('Unsubscribed', $result['datasets'][4]['label']);
|
||||
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, 1], $result['datasets'][4]['data']);
|
||||
$this->assertEquals('Bounced', $result['datasets'][5]['label']);
|
||||
$this->assertEquals([0, 0, 0, 0, 0, 0, 0, 1], $result['datasets'][5]['data']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Lead[]
|
||||
*/
|
||||
private function generateContacts(int $howMany): array
|
||||
{
|
||||
$contacts = [];
|
||||
|
||||
for ($i = 0; $i < $howMany; ++$i) {
|
||||
$contact = new Lead();
|
||||
$contact->setEmail("test{$i}@some.email");
|
||||
$contacts[] = $contact;
|
||||
}
|
||||
|
||||
$contactModel = static::getContainer()->get('mautic.lead.model.lead');
|
||||
\assert($contactModel instanceof LeadModel);
|
||||
$contactModel->saveEntities($contacts);
|
||||
|
||||
return $contacts;
|
||||
}
|
||||
|
||||
private function createSegment(): LeadList
|
||||
{
|
||||
$segment = new LeadList();
|
||||
$segment->setName('Segment A');
|
||||
$segment->setPublicName('Segment A');
|
||||
$segment->setAlias('segment-a');
|
||||
$this->em->persist($segment);
|
||||
$this->em->flush();
|
||||
|
||||
return $segment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Lead[] $contacts
|
||||
*/
|
||||
private function addContactsToSegment(array $contacts, LeadList $segment): void
|
||||
{
|
||||
foreach ($contacts as $contact) {
|
||||
$reference = new ListLead();
|
||||
$reference->setLead($contact);
|
||||
$reference->setList($segment);
|
||||
$reference->setDateAdded(new \DateTime());
|
||||
$this->em->persist($reference);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
private function createEmail(LeadList $segment): Email
|
||||
{
|
||||
$email = new Email();
|
||||
$email->setName('Email');
|
||||
$email->setSubject('Email Subject');
|
||||
$email->setCustomHtml('Email content');
|
||||
$email->setEmailType('list');
|
||||
$email->setPublishUp(new \DateTime('-1 day'));
|
||||
$email->setContinueSending(true);
|
||||
$email->setIsPublished(true);
|
||||
$email->addList($segment);
|
||||
$this->em->persist($email);
|
||||
$this->em->flush();
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
public function testSendEmailToLists(): void
|
||||
{
|
||||
$contacts = $this->generateContacts(10);
|
||||
$segment = $this->createSegment();
|
||||
$this->addContactsToSegment($contacts, $segment);
|
||||
$email = $this->createEmail($segment);
|
||||
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment], 4, 2);
|
||||
$this->assertEquals($sentCount, 4);
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment], 3, 2);
|
||||
$this->assertEquals($sentCount, 3);
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment], 2);
|
||||
$this->assertEquals($sentCount, 2);
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment], 4);
|
||||
$this->assertEquals($sentCount, 1);
|
||||
|
||||
$email = $this->createEmail($segment);
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment]);
|
||||
$this->assertEquals($sentCount, 10);
|
||||
|
||||
$email = $this->createEmail($segment);
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment], null, 2);
|
||||
$this->assertEquals($sentCount, 10);
|
||||
}
|
||||
|
||||
public function testSendEmailToListsWithContinueSendingFalse(): void
|
||||
{
|
||||
$contacts = $this->generateContacts(5);
|
||||
$segment = $this->createSegment();
|
||||
$this->addContactsToSegment($contacts, $segment);
|
||||
|
||||
// Create email with continueSending = false
|
||||
$email = new Email();
|
||||
$email->setName('Email with Continue Sending False');
|
||||
$email->setSubject('Email Subject');
|
||||
$email->setCustomHtml('Email content');
|
||||
$email->setEmailType('list');
|
||||
$email->setPublishUp(new \DateTime('-1 day'));
|
||||
$email->setContinueSending(false); // This should prevent sending
|
||||
$email->setIsPublished(true);
|
||||
$email->addList($segment);
|
||||
$this->em->persist($email);
|
||||
$this->em->flush();
|
||||
|
||||
// Attempt to send emails - should send 0 because continueSending is false
|
||||
[$sentCount, $failedCount, $failedRecipientsByList] = $this->emailModel->sendEmailToLists($email, [$segment]);
|
||||
$this->assertEquals(0, $sentCount, 'No emails should be sent when continueSending is false');
|
||||
$this->assertEquals(0, $failedCount, 'No emails should fail when continueSending is false');
|
||||
$this->assertEmpty($failedRecipientsByList, 'No failed recipients when continueSending is false');
|
||||
}
|
||||
|
||||
public function testNotOverwriteChildrenTranslationEmailAfterSaveParent(): void
|
||||
{
|
||||
$segment = new LeadList();
|
||||
$segmentName = 'Test_segment';
|
||||
$segment->setName($segmentName);
|
||||
$segment->setPublicName($segmentName);
|
||||
$segment->setAlias($segmentName);
|
||||
$this->em->persist($segment);
|
||||
|
||||
$emailName = 'Test';
|
||||
$customHtmlParent = 'test EN';
|
||||
$parentEmail = new Email();
|
||||
$parentEmail->setName($emailName);
|
||||
$parentEmail->setSubject($emailName);
|
||||
$parentEmail->setCustomHTML($customHtmlParent);
|
||||
$parentEmail->setEmailType('template');
|
||||
$parentEmail->setLanguage('en');
|
||||
$this->em->persist($parentEmail);
|
||||
|
||||
$customHtmlChildren = 'test FR';
|
||||
$childrenEmail = clone $parentEmail;
|
||||
$childrenEmail->setLanguage('fr');
|
||||
$childrenEmail->setCustomHTML($customHtmlChildren);
|
||||
$childrenEmail->setTranslationParent($parentEmail);
|
||||
$this->em->persist($parentEmail);
|
||||
|
||||
$this->em->detach($segment);
|
||||
$this->em->detach($parentEmail);
|
||||
$this->em->detach($childrenEmail);
|
||||
|
||||
$parentEmail->setName('Test change');
|
||||
$this->emailModel->saveEntity($parentEmail);
|
||||
|
||||
self::assertSame($customHtmlParent, $parentEmail->getCustomHtml());
|
||||
self::assertSame($customHtmlChildren, $childrenEmail->getCustomHtml());
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
* @throws ORMException
|
||||
*/
|
||||
private function emulateEmailStat(Lead $lead, Email $email, bool $isRead): void
|
||||
{
|
||||
$stat = new Stat();
|
||||
$stat->setEmailAddress('test@test.com');
|
||||
$stat->setLead($lead);
|
||||
$stat->setDateSent(new \DateTime('2023-07-22'));
|
||||
$stat->setEmail($email);
|
||||
$stat->setIsRead($isRead);
|
||||
$this->em->persist($stat);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
* @throws ORMException
|
||||
*/
|
||||
private function emulateClick(Lead $lead, Email $email, int $hits, int $uniqueHits): void
|
||||
{
|
||||
$ipAddress = new IpAddress();
|
||||
$ipAddress->setIpAddress('127.0.0.1');
|
||||
$this->em->persist($ipAddress);
|
||||
$this->em->flush();
|
||||
|
||||
$redirect = new Redirect();
|
||||
$redirect->setRedirectId(uniqid());
|
||||
$redirect->setUrl('https://example.com');
|
||||
$redirect->setHits($hits);
|
||||
$redirect->setUniqueHits($uniqueHits);
|
||||
$this->em->persist($redirect);
|
||||
|
||||
$trackable = new Trackable();
|
||||
$trackable->setChannelId($email->getId());
|
||||
$trackable->setChannel('email');
|
||||
$trackable->setHits($hits);
|
||||
$trackable->setUniqueHits($uniqueHits);
|
||||
$trackable->setRedirect($redirect);
|
||||
$this->em->persist($trackable);
|
||||
|
||||
$pageHit = new Hit();
|
||||
$pageHit->setRedirect($redirect);
|
||||
$pageHit->setIpAddress($ipAddress);
|
||||
$pageHit->setEmail($email);
|
||||
$pageHit->setLead($lead);
|
||||
$pageHit->setDateHit(new \DateTime());
|
||||
$pageHit->setCode(200);
|
||||
$pageHit->setUrl($redirect->getUrl());
|
||||
$pageHit->setTrackingId($redirect->getRedirectId());
|
||||
$pageHit->setSource('email');
|
||||
$pageHit->setSourceId($email->getId());
|
||||
$this->em->persist($pageHit);
|
||||
}
|
||||
|
||||
private function emulateEmailRead(Stat $emailStat): void
|
||||
{
|
||||
$emailStat->setIsRead(true);
|
||||
$emailStat->setDateRead(new \DateTime());
|
||||
$emailStat->setOpenCount(1);
|
||||
$email = $emailStat->getEmail();
|
||||
$email->setReadCount($email->getReadCount() + 1);
|
||||
$this->em->persist($emailStat);
|
||||
$this->em->persist($email);
|
||||
}
|
||||
|
||||
private function emulateEmailFailed(Stat $emailStat): void
|
||||
{
|
||||
$emailStat->setIsFailed(true);
|
||||
$this->em->persist($emailStat);
|
||||
}
|
||||
|
||||
private function createDnc(string $channel, Lead $contact, int $reason, ?int $channelId = null): DoNotContact
|
||||
{
|
||||
$dnc = new DoNotContact();
|
||||
$dnc->setChannel($channel);
|
||||
$dnc->setLead($contact);
|
||||
$dnc->setReason($reason);
|
||||
$dnc->setDateAdded(new \DateTime());
|
||||
if ($channelId) {
|
||||
$dnc->setChannelId($channelId);
|
||||
}
|
||||
$this->em->persist($dnc);
|
||||
|
||||
return $dnc;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ORMException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function testGetEmailCountryStatsSingleEmail(): void
|
||||
{
|
||||
/** @var EmailModel $emailModel */
|
||||
$emailModel = $this->getContainer()->get('mautic.email.model.email');
|
||||
$dateFrom = new \DateTimeImmutable('2023-07-21');
|
||||
$dateTo = new \DateTimeImmutable('2023-07-24');
|
||||
$leadsPayload = [
|
||||
[
|
||||
'email' => 'example1@test.com',
|
||||
'country' => 'Italy',
|
||||
'read' => true,
|
||||
'click' => true,
|
||||
],
|
||||
[
|
||||
'email' => 'example2@test.com',
|
||||
'country' => 'Italy',
|
||||
'read' => true,
|
||||
'click' => false,
|
||||
],
|
||||
[
|
||||
'email' => 'example3@test.com',
|
||||
'country' => 'Italy',
|
||||
'read' => false,
|
||||
'click' => false,
|
||||
],
|
||||
[
|
||||
'email' => 'example4@test.com',
|
||||
'country' => '',
|
||||
'read' => true,
|
||||
'click' => true,
|
||||
],
|
||||
[
|
||||
'email' => 'example5@test.com',
|
||||
'country' => 'Poland',
|
||||
'read' => true,
|
||||
'click' => false,
|
||||
],
|
||||
[
|
||||
'email' => 'example6@test.com',
|
||||
'country' => 'Poland',
|
||||
'read' => true,
|
||||
'click' => true,
|
||||
],
|
||||
];
|
||||
|
||||
$email = new Email();
|
||||
$email->setName('Test email');
|
||||
$this->em->persist($email);
|
||||
$this->em->flush();
|
||||
|
||||
foreach ($leadsPayload as $l) {
|
||||
$lead = new Lead();
|
||||
$lead->setEmail($l['email']);
|
||||
$lead->setCountry($l['country']);
|
||||
$this->em->persist($lead);
|
||||
|
||||
$this->emulateEmailStat($lead, $email, $l['read']);
|
||||
|
||||
if ($l['read'] && $l['click']) {
|
||||
$hits = rand(1, 5);
|
||||
$uniqueHits = rand(1, $hits);
|
||||
$this->emulateClick($lead, $email, $hits, $uniqueHits);
|
||||
}
|
||||
}
|
||||
$this->em->flush();
|
||||
$results = $emailModel->getCountryStats($email, $dateFrom, $dateTo);
|
||||
|
||||
$this->assertCount(2, $results);
|
||||
$this->assertSame([
|
||||
'clicked_through_count' => [
|
||||
[
|
||||
'clicked_through_count' => '1',
|
||||
'country' => '',
|
||||
],
|
||||
[
|
||||
'clicked_through_count' => '1',
|
||||
'country' => 'Italy',
|
||||
],
|
||||
[
|
||||
'clicked_through_count' => '1',
|
||||
'country' => 'Poland',
|
||||
],
|
||||
],
|
||||
'read_count' => [
|
||||
[
|
||||
'read_count' => '1',
|
||||
'country' => '',
|
||||
],
|
||||
[
|
||||
'read_count' => '2',
|
||||
'country' => 'Italy',
|
||||
],
|
||||
[
|
||||
'read_count' => '2',
|
||||
'country' => 'Poland',
|
||||
],
|
||||
],
|
||||
], $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws OptimisticLockException
|
||||
* @throws ORMException
|
||||
*/
|
||||
public function testGetContextEntity(): void
|
||||
{
|
||||
$email = new Email();
|
||||
$email->setName('Test email');
|
||||
$this->em->persist($email);
|
||||
$this->em->flush();
|
||||
|
||||
$id = $email->getId();
|
||||
$result = $this->emailModel->getEntity($id);
|
||||
|
||||
$this->assertSame($email, $result);
|
||||
}
|
||||
|
||||
public function testReturnsContactAsIsIfNoId(): void
|
||||
{
|
||||
$contact = ['email' => 'test@example.com'];
|
||||
|
||||
$result = $this->emailModel->enrichedContactWithCompanies($contact);
|
||||
|
||||
$this->assertSame($contact, $result);
|
||||
}
|
||||
|
||||
public function testReturnsContactAsIsIfCompaniesAlreadySet(): void
|
||||
{
|
||||
$contact = [
|
||||
'id' => 1,
|
||||
'companies' => ['company1'],
|
||||
];
|
||||
|
||||
$result = $this->emailModel->enrichedContactWithCompanies($contact);
|
||||
|
||||
$this->assertSame($contact, $result);
|
||||
}
|
||||
|
||||
public function testEnrichesContactWithCompanies(): void
|
||||
{
|
||||
$company = $this->createCompany('Mautic', 'hello@mautic.org');
|
||||
$company->setCity('Pune');
|
||||
$company->setCountry('India');
|
||||
|
||||
$this->em->persist($company);
|
||||
|
||||
$contact = $this->createLead('John', 'Doe', 'test@domain.tld');
|
||||
$this->createPrimaryCompanyForLead($contact, $company);
|
||||
$this->em->flush();
|
||||
|
||||
$contactArray = $contact->convertToArray();
|
||||
|
||||
$result = $this->emailModel->enrichedContactWithCompanies($contactArray);
|
||||
|
||||
$this->assertArrayHasKey('companies', $result);
|
||||
$this->assertSame($company->getName(), $result['companies'][0]['companyname']);
|
||||
$this->assertSame($company->getCity(), $result['companies'][0]['companycity']);
|
||||
$this->assertSame($company->getCountry(), $result['companies'][0]['companycountry']);
|
||||
}
|
||||
|
||||
public function testEnrichesContactWithEmptyCompaniesIfNoneFound(): void
|
||||
{
|
||||
$contact = $this->createLead('John', 'Doe', 'test@domain.tld');
|
||||
$this->em->flush();
|
||||
|
||||
$contactArray = $contact->convertToArray();
|
||||
|
||||
$result = $this->emailModel->enrichedContactWithCompanies($contactArray);
|
||||
|
||||
$this->assertArrayHasKey('companies', $result);
|
||||
$this->assertEmpty($result['companies']);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\EmailBundle\EmailEvents;
|
||||
use Mautic\EmailBundle\Entity\Stat;
|
||||
use Mautic\EmailBundle\Entity\StatRepository;
|
||||
use Mautic\EmailBundle\Model\EmailStatModel;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
final class EmailStatModelTest extends TestCase
|
||||
{
|
||||
public function testSave(): void
|
||||
{
|
||||
/** @var MockObject&EntityManager */
|
||||
$entityManager = $this->createMock(EntityManager::class);
|
||||
|
||||
/** @var MockObject&StatRepository */
|
||||
$statRepository = $this->createMock(StatRepository::class);
|
||||
|
||||
$entityManager->method('getRepository')->willReturn($statRepository);
|
||||
|
||||
$statRepository->expects($this->once())
|
||||
->method('saveEntities')
|
||||
->willReturnCallback(
|
||||
function (array $entities) {
|
||||
Assert::assertCount(1, $entities);
|
||||
Assert::assertInstanceOf(StatTest::class, $entities[0]);
|
||||
|
||||
// Emulate database adding the entity some autoincrement ID.
|
||||
$entities[0]->setId('123');
|
||||
}
|
||||
);
|
||||
|
||||
$dispatcher = new class extends EventDispatcher {
|
||||
public int $dispatchMethodCounter = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function dispatch(object $event, ?string $eventName = null): object
|
||||
{
|
||||
switch ($this->dispatchMethodCounter) {
|
||||
case 0:
|
||||
Assert::assertSame(EmailEvents::ON_EMAIL_STAT_PRE_SAVE, $eventName);
|
||||
Assert::assertCount(1, $event->getStats());
|
||||
Assert::assertNull($event->getStats()[0]->getId());
|
||||
break;
|
||||
|
||||
case 1:
|
||||
Assert::assertSame(EmailEvents::ON_EMAIL_STAT_POST_SAVE, $eventName);
|
||||
Assert::assertCount(1, $event->getStats());
|
||||
Assert::assertSame('123', $event->getStats()[0]->getId());
|
||||
break;
|
||||
}
|
||||
++$this->dispatchMethodCounter;
|
||||
|
||||
return $event;
|
||||
}
|
||||
};
|
||||
|
||||
$emailStatModel = new EmailStatModel($entityManager, $dispatcher);
|
||||
|
||||
$emailStat = new StatTest();
|
||||
|
||||
$emailStatModel->saveEntity($emailStat);
|
||||
|
||||
Assert::assertSame(2, $dispatcher->dispatchMethodCounter);
|
||||
}
|
||||
}
|
||||
|
||||
class StatTest extends Stat
|
||||
{
|
||||
private ?string $id = null;
|
||||
|
||||
public function setId(string $id): void
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
public function getId(): ?string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,760 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\AssetBundle\Model\AssetModel;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\CoreBundle\Helper\ThemeHelper;
|
||||
use Mautic\EmailBundle\Entity\CopyRepository;
|
||||
use Mautic\EmailBundle\Entity\Email;
|
||||
use Mautic\EmailBundle\Entity\Stat;
|
||||
use Mautic\EmailBundle\Event\EmailSendEvent;
|
||||
use Mautic\EmailBundle\Exception\FailedToSendToContactException;
|
||||
use Mautic\EmailBundle\Helper\DTO\AddressDTO;
|
||||
use Mautic\EmailBundle\Helper\FromEmailHelper;
|
||||
use Mautic\EmailBundle\Helper\MailHashHelper;
|
||||
use Mautic\EmailBundle\Helper\MailHelper;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use Mautic\EmailBundle\Model\EmailStatModel;
|
||||
use Mautic\EmailBundle\Model\SendEmailToContact;
|
||||
use Mautic\EmailBundle\MonitoredEmail\Mailbox;
|
||||
use Mautic\EmailBundle\Stat\StatHelper;
|
||||
use Mautic\EmailBundle\Tests\Helper\Transport\BatchTransport;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\DoNotContact;
|
||||
use Mautic\PageBundle\Model\RedirectModel;
|
||||
use Mautic\PageBundle\Model\TrackableModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Mailer\Mailer;
|
||||
use Symfony\Component\Routing\Router;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
use Twig\Environment;
|
||||
|
||||
class SendEmailToContactTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var array<array<string,int|string>>
|
||||
*/
|
||||
private array $contacts = [
|
||||
[
|
||||
'id' => 1,
|
||||
'email' => 'contact1@somewhere.com',
|
||||
'firstname' => 'Contact',
|
||||
'lastname' => '1',
|
||||
'owner_id' => 1,
|
||||
],
|
||||
[
|
||||
'id' => 2,
|
||||
'email' => 'contact2@somewhere.com',
|
||||
'firstname' => 'Contact',
|
||||
'lastname' => '2',
|
||||
'owner_id' => 0,
|
||||
],
|
||||
[
|
||||
'id' => 3,
|
||||
'email' => 'contact3@somewhere.com',
|
||||
'firstname' => 'Contact',
|
||||
'lastname' => '3',
|
||||
'owner_id' => 2,
|
||||
],
|
||||
[
|
||||
'id' => 4,
|
||||
'email' => 'contact4@somewhere.com',
|
||||
'firstname' => 'Contact',
|
||||
'lastname' => '4',
|
||||
'owner_id' => 1,
|
||||
],
|
||||
];
|
||||
|
||||
/**
|
||||
* @var MockObject&FromEmailHelper
|
||||
*/
|
||||
private $fromEmaiHelper;
|
||||
|
||||
/**
|
||||
* @var MockObject&CoreParametersHelper
|
||||
*/
|
||||
private $coreParametersHelper;
|
||||
|
||||
/**
|
||||
* @var MockObject&Mailbox
|
||||
*/
|
||||
private $mailbox;
|
||||
|
||||
/**
|
||||
* @var MockObject&LoggerInterface
|
||||
*/
|
||||
private MockObject $loggerMock;
|
||||
|
||||
private MailHashHelper $mailHashHelper;
|
||||
|
||||
/**
|
||||
* @var MockObject&TranslatorInterface
|
||||
*/
|
||||
private MockObject $translator;
|
||||
|
||||
/**
|
||||
* @var MockObject|MailHelper
|
||||
*/
|
||||
private $mailHelper;
|
||||
|
||||
/**
|
||||
* @var MockObject|DoNotContact
|
||||
*/
|
||||
private $dncModel;
|
||||
|
||||
/**
|
||||
* @var MockObject|EmailStatModel
|
||||
*/
|
||||
private $emailStatModel;
|
||||
|
||||
/**
|
||||
* @var MockObject&Environment
|
||||
*/
|
||||
private MockObject $twig;
|
||||
|
||||
private StatHelper $statHelper;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->dncModel = $this->createMock(DoNotContact::class);
|
||||
$this->mailHelper = $this->createMock(MailHelper::class);
|
||||
$this->emailStatModel = $this->createMock(EmailStatModel::class);
|
||||
$this->statHelper = new StatHelper($this->emailStatModel);
|
||||
$this->fromEmaiHelper = $this->createMock(FromEmailHelper::class);
|
||||
$this->coreParametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
$this->mailbox = $this->createMock(Mailbox::class);
|
||||
$this->loggerMock = $this->createMock(LoggerInterface::class);
|
||||
$this->mailHashHelper = new MailHashHelper($this->coreParametersHelper);
|
||||
$this->translator = $this->createMock(TranslatorInterface::class);
|
||||
$this->twig = $this->createMock(Environment::class);
|
||||
|
||||
$this->fromEmaiHelper->method('getFrom')
|
||||
->willReturn(new AddressDTO('someone@somewhere.com'));
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Tests that all contacts are temporarily failed if an Email entity happens to be incorrectly configured')]
|
||||
public function testContactsAreFailedIfSettingEmailEntityFails(): void
|
||||
{
|
||||
$this->mailHelper->method('setEmail')
|
||||
->willReturn(false);
|
||||
|
||||
// This should not be called because contact emails are just fine; the problem is with the email entity
|
||||
$this->dncModel->expects($this->never())
|
||||
->method('addDncForContact');
|
||||
|
||||
$model = new SendEmailToContact($this->mailHelper, $this->statHelper, $this->dncModel, $this->translator);
|
||||
|
||||
$email = new Email();
|
||||
$model->setEmail($email);
|
||||
|
||||
foreach ($this->contacts as $contact) {
|
||||
try {
|
||||
$model->setContact($contact)
|
||||
->send();
|
||||
} catch (FailedToSendToContactException) {
|
||||
}
|
||||
}
|
||||
|
||||
$model->finalFlush();
|
||||
|
||||
$failedContacts = $model->getFailedContacts();
|
||||
|
||||
$this->assertCount(4, $failedContacts);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Tests that bad emails are failed')]
|
||||
public function testExceptionIsThrownIfEmailIsSentToBadContact(): void
|
||||
{
|
||||
$emailMock = $this->createMock(Email::class);
|
||||
$emailMock
|
||||
->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$this->mailHelper->method('setEmail')
|
||||
->willReturn(true);
|
||||
$this->mailHelper->method('addTo')
|
||||
->willReturnCallback(
|
||||
fn ($email) => '@bad.com' !== $email
|
||||
);
|
||||
$this->mailHelper->method('queue')
|
||||
->willReturn([true, []]);
|
||||
|
||||
$stat = new Stat();
|
||||
$stat->setEmail($emailMock);
|
||||
$this->mailHelper->method('createEmailStat')
|
||||
->willReturn($stat);
|
||||
|
||||
$this->dncModel->expects($this->once())
|
||||
->method('addDncForContact');
|
||||
|
||||
$model = new SendEmailToContact($this->mailHelper, $this->statHelper, $this->dncModel, $this->translator);
|
||||
$model->setEmail($emailMock);
|
||||
|
||||
$contacts = $this->contacts;
|
||||
$contacts[0]['email'] = '@bad.com';
|
||||
|
||||
$exceptionThrown = false;
|
||||
foreach ($contacts as $contact) {
|
||||
try {
|
||||
$model->setContact($contact)
|
||||
->send();
|
||||
} catch (FailedToSendToContactException) {
|
||||
$exceptionThrown = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$exceptionThrown) {
|
||||
$this->fail('FailedToSendToContactException not thrown');
|
||||
}
|
||||
|
||||
$model->finalFlush();
|
||||
|
||||
$failedContacts = $model->getFailedContacts();
|
||||
|
||||
$this->assertCount(1, $failedContacts);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test a tokenized transport that limits batches does not throw BatchQueueMaxException on subsequent contacts when one fails')]
|
||||
public function testBadEmailDoesNotCauseBatchQueueMaxExceptionOnSubsequentContacts(): void
|
||||
{
|
||||
$emailMock = $this->createMock(Email::class);
|
||||
$emailMock->method('getId')->willReturn(1);
|
||||
$emailMock->method('getFromAddress')->willReturn('test@mautic.com');
|
||||
$emailMock->method('getSubject')->willReturn('Subject');
|
||||
$emailMock->method('getCustomHtml')->willReturn('content');
|
||||
|
||||
// Use our test token transport limiting to 1 recipient per queue
|
||||
$transport = new BatchTransport(false, 1);
|
||||
$mailer = new Mailer($transport);
|
||||
|
||||
$routerMock = $this->createMock(Router::class);
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
|
||||
$this->fromEmaiHelper->method('getFromAddressConsideringOwner')
|
||||
->willReturn(new AddressDTO('someone@somewhere.com'));
|
||||
|
||||
$this->coreParametersHelper->method('get')->willReturnCallback(
|
||||
fn ($param) => match ($param) {
|
||||
'mailer_from_email' => 'nobody@nowhere.com',
|
||||
'secret_key' => 'secret',
|
||||
default => '',
|
||||
}
|
||||
);
|
||||
|
||||
$themeHelper = $this->createMock(ThemeHelper::class);
|
||||
$themeHelper->expects(self::never())
|
||||
->method('checkForTwigTemplate');
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects($this->never()) // Never to make sure that the mock is properly tested if needed.
|
||||
->method('getReference');
|
||||
|
||||
$mailHelper = $this->getMockBuilder(MailHelper::class)
|
||||
->setConstructorArgs([
|
||||
$mailer,
|
||||
$this->fromEmaiHelper,
|
||||
$this->coreParametersHelper,
|
||||
$this->mailbox,
|
||||
$this->loggerMock,
|
||||
$this->mailHashHelper,
|
||||
$routerMock,
|
||||
$this->twig,
|
||||
$themeHelper,
|
||||
$this->createMock(PathsHelper::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$requestStack,
|
||||
$entityManager,
|
||||
$this->createMock(ModelFactory::class),
|
||||
$this->createMock(AssetModel::class),
|
||||
$this->createMock(TrackableModel::class),
|
||||
$this->createMock(RedirectModel::class),
|
||||
])
|
||||
->onlyMethods(['createEmailStat'])
|
||||
->getMock();
|
||||
|
||||
$mailHelper->method('createEmailStat')
|
||||
->willReturnCallback(
|
||||
function () use ($emailMock) {
|
||||
$stat = new Stat();
|
||||
$stat->setEmail($emailMock);
|
||||
|
||||
$leadMock = $this->createMock(Lead::class);
|
||||
$leadMock->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$stat->setLead($leadMock);
|
||||
|
||||
return $stat;
|
||||
}
|
||||
);
|
||||
|
||||
// Enable queueing
|
||||
$mailHelper->enableQueue();
|
||||
|
||||
$this->dncModel->expects($this->once())
|
||||
->method('addDncForContact');
|
||||
|
||||
$model = new SendEmailToContact($mailHelper, $this->statHelper, $this->dncModel, $this->translator);
|
||||
$model->setEmail($emailMock);
|
||||
|
||||
$contacts = $this->contacts;
|
||||
$contacts[0]['email'] = '@bad.com';
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
try {
|
||||
$model->setContact($contact)
|
||||
->send();
|
||||
} catch (FailedToSendToContactException) {
|
||||
// We're good here
|
||||
}
|
||||
}
|
||||
|
||||
$model->finalFlush();
|
||||
|
||||
$failedContacts = $model->getFailedContacts();
|
||||
|
||||
$this->assertCount(1, $failedContacts);
|
||||
|
||||
// Our fake transport should have processed 3 metadatas
|
||||
$this->assertCount(3, $transport->getMetadatas());
|
||||
|
||||
// We made it this far so all of the emails were processed despite a bad email in the batch
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test a tokenized transport that fills tokens correctly')]
|
||||
public function testBatchQueueContactsHaveTokensHydrated(): void
|
||||
{
|
||||
$this->coreParametersHelper->method('get')->willReturnMap([['mailer_from_email', null, 'nobody@nowhere.com'], ['secret_key', null, 'secret']]);
|
||||
|
||||
$emailMock = $this->createMock(Email::class);
|
||||
$emailMock->method('getId')->willReturn(1);
|
||||
$emailMock->method('getFromAddress')->willReturn('test@mautic.com');
|
||||
$emailMock->method('getSubject')->willReturn('Subject');
|
||||
$emailMock->method('getCustomHtml')->willReturn('Hi {contactfield=firstname}');
|
||||
|
||||
// Use our test token transport limiting to 1 recipient per queue
|
||||
$transport = new BatchTransport(false, 1);
|
||||
$mailer = new Mailer($transport);
|
||||
|
||||
// Mock factory to remove when factory is completely gone.
|
||||
$this->coreParametersHelper->method('get')
|
||||
->willReturnCallback(
|
||||
fn ($param) => match ($param) {
|
||||
default => '',
|
||||
}
|
||||
);
|
||||
|
||||
$mockDispatcher = $this->createMock(EventDispatcher::class);
|
||||
$mockDispatcher->method('dispatch')
|
||||
->willReturnCallback(
|
||||
function (EmailSendEvent $event, $eventName) {
|
||||
$lead = $event->getLead();
|
||||
|
||||
$tokens = [];
|
||||
foreach ($lead as $field => $value) {
|
||||
$tokens["{contactfield=$field}"] = $value;
|
||||
}
|
||||
$tokens['{hash}'] = $event->getIdHash();
|
||||
|
||||
$event->addTokens($tokens);
|
||||
|
||||
return $event;
|
||||
}
|
||||
);
|
||||
|
||||
$copyRepoMock = $this->createMock(CopyRepository::class);
|
||||
$emailModelMock = $this->createMock(EmailModel::class);
|
||||
$emailModelMock->method('getCopyRepository')
|
||||
->willReturn($copyRepoMock);
|
||||
|
||||
$modelFactory = $this->createMock(ModelFactory::class);
|
||||
$modelFactory->method('getModel')
|
||||
->with(EmailModel::class)
|
||||
->willReturn($emailModelMock);
|
||||
|
||||
$this->fromEmaiHelper->method('getFromAddressConsideringOwner')
|
||||
->willReturn(new AddressDTO('someone@somewhere.com'));
|
||||
|
||||
$themeHelper = $this->createMock(ThemeHelper::class);
|
||||
$themeHelper->expects(self::never())
|
||||
->method('checkForTwigTemplate');
|
||||
|
||||
$mailHelper = $this->getMockBuilder(MailHelper::class)
|
||||
->setConstructorArgs([
|
||||
$mailer,
|
||||
$this->fromEmaiHelper,
|
||||
$this->coreParametersHelper,
|
||||
$this->mailbox,
|
||||
$this->loggerMock,
|
||||
$this->mailHashHelper,
|
||||
$this->createMock(Router::class),
|
||||
$this->twig,
|
||||
$themeHelper,
|
||||
$this->createMock(PathsHelper::class),
|
||||
$mockDispatcher,
|
||||
new RequestStack(),
|
||||
$this->createMock(EntityManager::class),
|
||||
$modelFactory,
|
||||
$this->createMock(AssetModel::class),
|
||||
$this->createMock(TrackableModel::class),
|
||||
$this->createMock(RedirectModel::class),
|
||||
])
|
||||
->onlyMethods([])
|
||||
->getMock();
|
||||
|
||||
// Enable queueing
|
||||
$mailHelper->enableQueue();
|
||||
|
||||
$this->emailStatModel->method('saveEntity')
|
||||
->willReturnCallback(
|
||||
function (Stat $stat): void {
|
||||
$tokens = $stat->getTokens();
|
||||
$this->assertGreaterThan(1, count($tokens));
|
||||
$this->assertEquals($stat->getTrackingHash(), $tokens['{hash}']);
|
||||
}
|
||||
);
|
||||
|
||||
$model = new SendEmailToContact($mailHelper, $this->statHelper, $this->dncModel, $this->translator);
|
||||
$model->setEmail($emailMock);
|
||||
|
||||
foreach ($this->contacts as $contact) {
|
||||
try {
|
||||
$model->setContact($contact)
|
||||
->send();
|
||||
} catch (FailedToSendToContactException) {
|
||||
// We're good here
|
||||
}
|
||||
}
|
||||
|
||||
$model->finalFlush();
|
||||
|
||||
$this->assertCount(4, $transport->getMetadatas());
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that stat entries are saved in batches of 20')]
|
||||
public function testThatStatEntriesAreCreatedAndPersistedEveryBatch(): void
|
||||
{
|
||||
$this->coreParametersHelper->method('get')->willReturnMap([['mailer_from_email', null, 'nobody@nowhere.com'], ['secret_key', null, 'secret']]);
|
||||
|
||||
$emailMock = $this->createMock(Email::class);
|
||||
$emailMock->method('getId')->willReturn(1);
|
||||
$emailMock->method('getFromAddress')->willReturn('test@mautic.com');
|
||||
$emailMock->method('getSubject')->willReturn('Subject');
|
||||
$emailMock->method('getCustomHtml')->willReturn('content');
|
||||
|
||||
// Use our test token transport limiting to 1 recipient per queue
|
||||
$transport = new BatchTransport(false, 1);
|
||||
$mailer = new Mailer($transport);
|
||||
|
||||
$this->coreParametersHelper->method('get')
|
||||
->willReturnCallback(
|
||||
fn ($param) => match ($param) {
|
||||
default => '',
|
||||
}
|
||||
);
|
||||
$routerMock = $this->createMock(Router::class);
|
||||
|
||||
$this->fromEmaiHelper->method('getFromAddressConsideringOwner')
|
||||
->willReturn(new AddressDTO('someone@somewhere.com'));
|
||||
|
||||
$themeHelper = $this->createMock(ThemeHelper::class);
|
||||
$themeHelper->expects(self::never())
|
||||
->method('checkForTwigTemplate');
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects($this->never()) // Never to make sure that the mock is properly tested if needed.
|
||||
->method('getReference');
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
|
||||
$mailHelper = $this->getMockBuilder(MailHelper::class)
|
||||
->setConstructorArgs([
|
||||
$mailer,
|
||||
$this->fromEmaiHelper,
|
||||
$this->coreParametersHelper,
|
||||
$this->mailbox,
|
||||
$this->loggerMock,
|
||||
$this->mailHashHelper,
|
||||
$routerMock,
|
||||
$this->twig,
|
||||
$themeHelper,
|
||||
$this->createMock(PathsHelper::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$requestStack,
|
||||
$entityManager,
|
||||
$this->createMock(ModelFactory::class),
|
||||
$this->createMock(AssetModel::class),
|
||||
$this->createMock(TrackableModel::class),
|
||||
$this->createMock(RedirectModel::class),
|
||||
])
|
||||
->onlyMethods(['createEmailStat'])
|
||||
->getMock();
|
||||
|
||||
$mailHelper->expects($this->exactly(21))
|
||||
->method('createEmailStat')
|
||||
->willReturnCallback(
|
||||
function () use ($emailMock) {
|
||||
$stat = new Stat();
|
||||
$stat->setEmail($emailMock);
|
||||
|
||||
$leadMock = $this->createMock(Lead::class);
|
||||
$leadMock->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$stat->setLead($leadMock);
|
||||
|
||||
return $stat;
|
||||
}
|
||||
);
|
||||
|
||||
// Enable queueing
|
||||
$mailHelper->enableQueue();
|
||||
|
||||
// Here's the test; this should be called after 20 contacts are processed
|
||||
$this->emailStatModel->expects($this->exactly(21))
|
||||
->method('saveEntity');
|
||||
|
||||
$this->dncModel->expects($this->never())
|
||||
->method('addDncForContact');
|
||||
|
||||
$model = new SendEmailToContact($mailHelper, $this->statHelper, $this->dncModel, $this->translator);
|
||||
$model->setEmail($emailMock);
|
||||
|
||||
// Let's generate 20 bogus contacts
|
||||
$contacts = [];
|
||||
$counter = 0;
|
||||
while ($counter <= 20) {
|
||||
$contacts[] = [
|
||||
'id' => $counter,
|
||||
'email' => 'email'.uniqid().'@somewhere.com',
|
||||
'firstname' => 'Contact',
|
||||
'lastname' => uniqid(),
|
||||
];
|
||||
|
||||
++$counter;
|
||||
}
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
try {
|
||||
$model->setContact($contact)
|
||||
->send();
|
||||
} catch (FailedToSendToContactException $exception) {
|
||||
$this->fail('FailedToSendToContactException thrown: '.$exception->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$model->finalFlush();
|
||||
|
||||
$failedContacts = $model->getFailedContacts();
|
||||
$this->assertCount(0, $failedContacts);
|
||||
$this->assertCount(21, $transport->getMetadatas());
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that a failed email from the transport is handled')]
|
||||
public function testThatAFailureFromTransportIsHandled(): void
|
||||
{
|
||||
$this->coreParametersHelper->method('get')->willReturnMap([['mailer_from_email', null, 'nobody@nowhere.com'], ['secret_key', null, 'secret']]);
|
||||
|
||||
$emailMock = $this->createMock(Email::class);
|
||||
$emailMock->method('getId')->willReturn(1);
|
||||
$emailMock->method('getFromAddress')->willReturn('test@mautic.com');
|
||||
$emailMock->method('getSubject')->willReturn(''); // The subject must be empty for the email to fail.
|
||||
$emailMock->method('getCustomHtml')->willReturn('content');
|
||||
|
||||
// Use our test token transport limiting to 1 recipient per queue
|
||||
$transport = new BatchTransport(true, 1);
|
||||
$mailer = new Mailer($transport);
|
||||
|
||||
$this->coreParametersHelper->method('get')
|
||||
->willReturnCallback(
|
||||
fn ($param) => match ($param) {
|
||||
default => '',
|
||||
}
|
||||
);
|
||||
|
||||
$this->fromEmaiHelper->method('getFromAddressConsideringOwner')->willReturn(new AddressDTO('someone@somewhere.com'));
|
||||
$routerMock = $this->createMock(Router::class);
|
||||
$twig = $this->createMock(Environment::class);
|
||||
|
||||
$themeHelper = $this->createMock(ThemeHelper::class);
|
||||
$themeHelper->expects(self::never())
|
||||
->method('checkForTwigTemplate');
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects($this->never()) // Never to make sure that the mock is properly tested if needed.
|
||||
->method('getReference');
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
|
||||
/** @var MockObject&MailHelper $mailHelper */
|
||||
$mailHelper = $this->getMockBuilder(MailHelper::class)
|
||||
->setConstructorArgs([
|
||||
$mailer,
|
||||
$this->fromEmaiHelper,
|
||||
$this->coreParametersHelper,
|
||||
$this->mailbox,
|
||||
$this->loggerMock,
|
||||
$this->mailHashHelper,
|
||||
$routerMock,
|
||||
$this->twig,
|
||||
$themeHelper,
|
||||
$this->createMock(PathsHelper::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$requestStack,
|
||||
$entityManager,
|
||||
$this->createMock(ModelFactory::class),
|
||||
$this->createMock(AssetModel::class),
|
||||
$this->createMock(TrackableModel::class),
|
||||
$this->createMock(RedirectModel::class),
|
||||
])
|
||||
->onlyMethods(['createEmailStat'])
|
||||
->getMock();
|
||||
|
||||
$mailHelper->method('createEmailStat')
|
||||
->willReturnCallback(
|
||||
function () use ($emailMock) {
|
||||
$stat = new Stat();
|
||||
$stat->setEmail($emailMock);
|
||||
|
||||
$leadMock = $this->createMock(Lead::class);
|
||||
$leadMock->method('getId')->willReturn(1);
|
||||
|
||||
$stat->setLead($leadMock);
|
||||
|
||||
return $stat;
|
||||
}
|
||||
);
|
||||
|
||||
// Enable queueing
|
||||
$mailHelper->enableQueue();
|
||||
|
||||
$this->dncModel->expects($this->never())->method('addDncForContact');
|
||||
|
||||
$model = new SendEmailToContact($mailHelper, $this->statHelper, $this->dncModel, $this->translator);
|
||||
$model->setEmail($emailMock);
|
||||
|
||||
foreach ($this->contacts as $contact) {
|
||||
try {
|
||||
$model->setContact($contact)->send();
|
||||
} catch (FailedToSendToContactException) {
|
||||
// We're good here
|
||||
}
|
||||
}
|
||||
|
||||
$model->finalFlush();
|
||||
|
||||
$failedContacts = $model->getFailedContacts();
|
||||
|
||||
$this->assertCount(1, $failedContacts);
|
||||
|
||||
$counts = $model->getSentCounts();
|
||||
|
||||
// Should have increased to 4, one failed via the transport so back down to 3
|
||||
$this->assertEquals(3, $counts[1]);
|
||||
|
||||
// One error message from the transport
|
||||
$errorMessages = $model->getErrors();
|
||||
$this->assertCount(1, $errorMessages);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Test that sending an email with invalid Bcc address is handled')]
|
||||
public function testThatInvalidBccFailureIsHandled(): void
|
||||
{
|
||||
defined('MAUTIC_ENV') or define('MAUTIC_ENV', 'test');
|
||||
|
||||
/** @var MockObject&FromEmailHelper $fromEmailHelper */
|
||||
$fromEmailHelper = $this->createMock(FromEmailHelper::class);
|
||||
|
||||
/** @var MockObject&CoreParametersHelper $coreParametersHelper */
|
||||
$coreParametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
|
||||
/** @var MockObject&Mailbox $mailbox */
|
||||
$mailbox = $this->createMock(Mailbox::class);
|
||||
|
||||
/** @var MockObject&LoggerInterface $logger */
|
||||
$logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
/** @var MockObject&RouterInterface $router */
|
||||
$router = $this->createMock(RouterInterface::class);
|
||||
|
||||
/** @var MockObject&Environment $twig */
|
||||
$twig = $this->createMock(Environment::class);
|
||||
|
||||
$themeHelper = $this->createMock(ThemeHelper::class);
|
||||
$themeHelper->expects(self::never())
|
||||
->method('checkForTwigTemplate');
|
||||
|
||||
$coreParametersHelper->method('get')
|
||||
->willReturnMap(
|
||||
[
|
||||
['mailer_from_email', null, 'nobody@nowhere.com'],
|
||||
['mailer_from_name', null, 'No Body'],
|
||||
['mailer_return_path', false, null],
|
||||
]
|
||||
);
|
||||
|
||||
$entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$entityManager->expects($this->never()) // Never to make sure that the mock is properly tested if needed.
|
||||
->method('getReference');
|
||||
|
||||
$requestStack = new RequestStack();
|
||||
|
||||
$mailer = new Mailer(new BatchTransport());
|
||||
$mailHelper = new MailHelper(
|
||||
$mailer,
|
||||
$fromEmailHelper,
|
||||
$coreParametersHelper,
|
||||
$mailbox,
|
||||
$logger,
|
||||
$this->mailHashHelper,
|
||||
$router,
|
||||
$twig,
|
||||
$themeHelper,
|
||||
$this->createMock(PathsHelper::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$requestStack,
|
||||
$entityManager,
|
||||
$this->createMock(ModelFactory::class),
|
||||
$this->createMock(AssetModel::class),
|
||||
$this->createMock(TrackableModel::class),
|
||||
$this->createMock(RedirectModel::class),
|
||||
);
|
||||
$dncModel = $this->createMock(DoNotContact::class);
|
||||
$translator = $this->createMock(TranslatorInterface::class);
|
||||
$model = new SendEmailToContact($mailHelper, $this->statHelper, $dncModel, $translator);
|
||||
$emailMock = $this->createMock(Email::class);
|
||||
$emailMock->method('getId')->willReturn(1);
|
||||
$emailMock->method('getSubject')->willReturn('subject');
|
||||
$emailMock->method('getCustomHtml')->willReturn('content');
|
||||
|
||||
// Set invalid BCC (should use comma as separator)
|
||||
$emailMock
|
||||
->expects($this->any())
|
||||
->method('getBccAddress')
|
||||
->willReturn('test@mautic.com; test@mautic.com');
|
||||
|
||||
$model->setEmail($emailMock);
|
||||
|
||||
$stat = new Stat();
|
||||
$stat->setEmail($emailMock);
|
||||
|
||||
$this->expectException(FailedToSendToContactException::class);
|
||||
$this->expectExceptionMessage('Email "test@mautic.com; test@mautic.com" does not comply with addr-spec of RFC 2822.');
|
||||
|
||||
// Send should trigger the FailedToSendToContactException
|
||||
$model->setContact($this->contacts[0])->send();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Event\TokenReplacementEvent;
|
||||
use Mautic\CoreBundle\Exception\RecordNotPublishedException;
|
||||
use Mautic\EmailBundle\EmailEvents;
|
||||
use Mautic\EmailBundle\Entity\Email;
|
||||
use Mautic\EmailBundle\Event\EmailSendEvent;
|
||||
use Mautic\EmailBundle\Exception\EmailCouldNotBeSentException;
|
||||
use Mautic\EmailBundle\Exception\InvalidEmailException;
|
||||
use Mautic\EmailBundle\Helper\EmailValidator;
|
||||
use Mautic\EmailBundle\Model\EmailModel;
|
||||
use Mautic\EmailBundle\Model\SendEmailToUser;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Validator\CustomFieldValidator;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class SendEmailToUserTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var MockObject|EmailModel
|
||||
*/
|
||||
private MockObject $emailModel;
|
||||
|
||||
/**
|
||||
* @var MockObject|EventDispatcherInterface
|
||||
*/
|
||||
private MockObject $dispatcher;
|
||||
|
||||
/**
|
||||
* @var MockObject|CustomFieldValidator
|
||||
*/
|
||||
private MockObject $customFieldValidator;
|
||||
|
||||
/**
|
||||
* @var MockObject|EmailValidator
|
||||
*/
|
||||
private MockObject $emailValidator;
|
||||
|
||||
private SendEmailToUser $sendEmailToUser;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->emailModel = $this->createMock(EmailModel::class);
|
||||
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->customFieldValidator = $this->createMock(CustomFieldValidator::class);
|
||||
$this->emailValidator = $this->createMock(EmailValidator::class);
|
||||
$this->sendEmailToUser = new SendEmailToUser(
|
||||
$this->emailModel,
|
||||
$this->dispatcher,
|
||||
$this->customFieldValidator,
|
||||
$this->emailValidator
|
||||
);
|
||||
}
|
||||
|
||||
public function testEmailNotFound(): void
|
||||
{
|
||||
$lead = new Lead();
|
||||
|
||||
$this->emailModel->expects($this->once())
|
||||
->method('getEntity')
|
||||
->with(100)
|
||||
->willReturn(null);
|
||||
|
||||
$config = [];
|
||||
$config['useremail']['email'] = 100;
|
||||
|
||||
$this->expectException(EmailCouldNotBeSentException::class);
|
||||
|
||||
$this->sendEmailToUser->sendEmailToUsers($config, $lead);
|
||||
}
|
||||
|
||||
public function testEmailNotPublished(): void
|
||||
{
|
||||
$lead = new Lead();
|
||||
$email = new Email();
|
||||
$email->setIsPublished(false);
|
||||
|
||||
$this->emailModel->expects($this->once())
|
||||
->method('getEntity')
|
||||
->with(100)
|
||||
->willREturn($email);
|
||||
|
||||
$config = [];
|
||||
$config['useremail']['email'] = 100;
|
||||
|
||||
$this->expectException(EmailCouldNotBeSentException::class);
|
||||
|
||||
$this->sendEmailToUser->sendEmailToUsers($config, $lead);
|
||||
}
|
||||
|
||||
public function testSendEmailWithNoError(): void
|
||||
{
|
||||
$lead = new Lead();
|
||||
$owner = new class extends User {
|
||||
public function getId(): int
|
||||
{
|
||||
return 10;
|
||||
}
|
||||
};
|
||||
|
||||
$lead->setOwner($owner);
|
||||
|
||||
$email = new Email();
|
||||
$email->setIsPublished(true);
|
||||
|
||||
$this->emailModel->expects($this->once())
|
||||
->method('getEntity')
|
||||
->with(33)
|
||||
->willReturn($email);
|
||||
|
||||
$emailSendEvent = new class extends EmailSendEvent {
|
||||
public int $getTokenMethodCallCounter = 0;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $includeGlobal
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getTokens($includeGlobal = true): array
|
||||
{
|
||||
++$this->getTokenMethodCallCounter;
|
||||
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
// Global token for Email
|
||||
$this->emailModel->expects($this->once())
|
||||
->method('dispatchEmailSendEvent')
|
||||
->willReturn($emailSendEvent);
|
||||
// Different handling of tokens in the To, BC, BCC fields.
|
||||
$matcher = $this->exactly(3);
|
||||
|
||||
// Different handling of tokens in the To, BC, BCC fields.
|
||||
$this->customFieldValidator->expects($matcher)
|
||||
->method('validateFieldType')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('unpublished-field', $parameters[0]);
|
||||
$this->assertSame('email', $parameters[1]);
|
||||
throw new RecordNotPublishedException();
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('unpublished-field', $parameters[0]);
|
||||
$this->assertSame('email', $parameters[1]);
|
||||
throw new RecordNotPublishedException();
|
||||
}
|
||||
if (3 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('active-field', $parameters[0]);
|
||||
$this->assertSame('email', $parameters[1]);
|
||||
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
// The event is dispatched only for valid tokens.
|
||||
$this->dispatcher->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with(
|
||||
$this->callback(
|
||||
function (TokenReplacementEvent $event) use ($lead) {
|
||||
Assert::assertSame('{contactfield=active-field}', $event->getContent());
|
||||
Assert::assertSame($lead, $event->getLead());
|
||||
|
||||
// Emulate a subscriber.
|
||||
$event->setContent('replaced.token@email.address');
|
||||
|
||||
return true;
|
||||
}
|
||||
),
|
||||
EmailEvents::ON_EMAIL_ADDRESS_TOKEN_REPLACEMENT,
|
||||
);
|
||||
$matcher = $this->exactly(4);
|
||||
|
||||
$this->emailValidator->expects($matcher)
|
||||
->method('validate')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('hello@there.com', $parameters[0]);
|
||||
|
||||
return null;
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('bob@bobek.cz', $parameters[0]);
|
||||
|
||||
return null;
|
||||
}
|
||||
if (3 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('hidden@translation.in', $parameters[0]);
|
||||
|
||||
return null;
|
||||
}
|
||||
if (4 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame('{invalid-token}', $parameters[0]);
|
||||
|
||||
return throw new InvalidEmailException('{invalid-token}');
|
||||
}
|
||||
});
|
||||
// Send email method
|
||||
|
||||
$this->emailModel
|
||||
->expects($this->once())
|
||||
->method('sendEmailToUser')
|
||||
->willReturnCallback(function ($email, $users, $leadCredentials, $tokens, $assetAttachments, $saveStat, $to, $cc, $bcc): void {
|
||||
$expectedUsers = [
|
||||
['id' => 6],
|
||||
['id' => 7],
|
||||
['id' => 10], // owner ID
|
||||
];
|
||||
$this->assertInstanceOf(Email::class, $email);
|
||||
$this->assertEquals($expectedUsers, $users);
|
||||
$this->assertFalse($saveStat);
|
||||
$this->assertEquals(['hello@there.com', 'bob@bobek.cz', 'default@email.com'], $to);
|
||||
$this->assertEquals([], $cc);
|
||||
$this->assertEquals([0 => 'hidden@translation.in', 2 => 'replaced.token@email.address'], $bcc);
|
||||
});
|
||||
|
||||
$config = [
|
||||
'useremail' => [
|
||||
'email' => 33,
|
||||
],
|
||||
'user_id' => [6, 7],
|
||||
'to_owner' => true,
|
||||
'to' => 'hello@there.com, bob@bobek.cz, {contactfield=unpublished-field|default@email.com}, {contactfield=unpublished-field}',
|
||||
'bcc' => 'hidden@translation.in,{invalid-token}, {contactfield=active-field}',
|
||||
];
|
||||
|
||||
$this->sendEmailToUser->sendEmailToUsers($config, $lead);
|
||||
|
||||
Assert::assertSame(1, $emailSendEvent->getTokenMethodCallCounter);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\EmailBundle\Tests\Model;
|
||||
|
||||
use Mautic\EmailBundle\Entity\Stat;
|
||||
use Mautic\EmailBundle\Model\EmailStatModel;
|
||||
use Mautic\EmailBundle\Model\TransportCallback;
|
||||
use Mautic\EmailBundle\MonitoredEmail\Search\ContactFinder;
|
||||
use Mautic\EmailBundle\MonitoredEmail\Search\Result;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact as DNC;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\DoNotContact;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class TransportCallbackTest extends TestCase
|
||||
{
|
||||
public function testStatSave(): void
|
||||
{
|
||||
$dncModel = new class extends DoNotContact {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function addDncForContact($contactId, $channel, $reason = DNC::BOUNCED, $comments = '', $persist = true, $checkCurrentStatus = true, $allowUnsubscribeOverride = false)
|
||||
{
|
||||
Assert::assertSame('email', $channel);
|
||||
Assert::assertSame(DNC::BOUNCED, $reason);
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
$contactFinder = new class extends ContactFinder {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function findByHash($hash): Result
|
||||
{
|
||||
Assert::assertSame('some-hash-id', $hash);
|
||||
|
||||
$result = new Result();
|
||||
$contact = new Lead();
|
||||
$stat = new Stat();
|
||||
$result->addContact($contact);
|
||||
$result->setStat($stat);
|
||||
|
||||
return $result;
|
||||
}
|
||||
};
|
||||
|
||||
$emailStatModel = new class extends EmailStatModel {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
public function saveEntity(Stat $stat): void
|
||||
{
|
||||
Assert::assertTrue($stat->isFailed());
|
||||
Assert::assertArrayHasKey('bounces', $stat->getOpenDetails());
|
||||
Assert::assertArrayHasKey(0, $stat->getOpenDetails()['bounces']);
|
||||
Assert::assertArrayHasKey('datetime', $stat->getOpenDetails()['bounces'][0]);
|
||||
Assert::assertArrayHasKey('reason', $stat->getOpenDetails()['bounces'][0]);
|
||||
Assert::assertSame('some-comments', $stat->getOpenDetails()['bounces'][0]['reason']);
|
||||
}
|
||||
};
|
||||
|
||||
$transportCallback = new TransportCallback($dncModel, $contactFinder, $emailStatModel);
|
||||
|
||||
$transportCallback->addFailureByHashId('some-hash-id', 'some-comments');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user