Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Command;
|
||||
|
||||
use Mautic\ChannelBundle\Entity\MessageQueue;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\CoreBundle\Tests\Functional\CreateTestEntitiesTrait;
|
||||
use Mautic\EmailBundle\Entity\Email;
|
||||
use Mautic\EmailBundle\Entity\Stat;
|
||||
use Mautic\EmailBundle\Entity\StatRepository;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
final class ProcessMarketingMessagesQueueCommandFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
use CreateTestEntitiesTrait;
|
||||
|
||||
public function testIdleCommand(): void
|
||||
{
|
||||
$commandTester = $this->testSymfonyCommand('mautic:messages:send');
|
||||
Assert::assertSame(0, $commandTester->getStatusCode());
|
||||
}
|
||||
|
||||
public function testCommandWithEmailQueue(): void
|
||||
{
|
||||
$email = $this->createEmail('Test Email');
|
||||
$this->em->flush();
|
||||
|
||||
$scheduledDate = new \DateTime('-10 minutes');
|
||||
$datePublished = new \DateTime('-1 day');
|
||||
|
||||
// Create 60 different leads and message queue items
|
||||
$leads = [];
|
||||
$messages = [];
|
||||
for ($i = 0; $i < 60; ++$i) {
|
||||
$leads[$i] = $this->createLead("John{$i}", "Doe{$i}", "jd{$i}@example.com");
|
||||
$this->em->persist($leads[$i]);
|
||||
}
|
||||
$this->em->flush();
|
||||
|
||||
// Create a message for each lead
|
||||
foreach ($leads as $lead) {
|
||||
$messages[] = $this->createMessageQueue($email, $lead, $scheduledDate, $datePublished);
|
||||
}
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$this->em->persist($message);
|
||||
}
|
||||
$this->em->flush();
|
||||
|
||||
$commandTester = $this->testSymfonyCommand('mautic:messages:send');
|
||||
Assert::assertSame(0, $commandTester->getStatusCode());
|
||||
Assert::assertStringContainsString('Messages sent: 60', $commandTester->getDisplay());
|
||||
|
||||
// Verify that stats were created for a sample of leads
|
||||
$this->assertEmailStatCreated($email, $leads[0]);
|
||||
$this->assertEmailStatCreated($email, $leads[29]);
|
||||
$this->assertEmailStatCreated($email, $leads[59]);
|
||||
}
|
||||
|
||||
public function testCommandWithLimitParameter(): void
|
||||
{
|
||||
$lead = $this->createLead('John', 'Doe', 'jd@example.com');
|
||||
$email1 = $this->createEmail('Test Email 1');
|
||||
$email2 = $this->createEmail('Test Email 2');
|
||||
$email3 = $this->createEmail('Test Email 3');
|
||||
$this->em->flush();
|
||||
|
||||
$scheduledDate = new \DateTime('-10 minutes');
|
||||
$datePublished = new \DateTime('-1 day');
|
||||
|
||||
$messages = [
|
||||
$this->createMessageQueue($email1, $lead, $scheduledDate, $datePublished),
|
||||
$this->createMessageQueue($email2, $lead, $scheduledDate, $datePublished),
|
||||
$this->createMessageQueue($email3, $lead, $scheduledDate, $datePublished),
|
||||
];
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$this->em->persist($message);
|
||||
}
|
||||
$this->em->flush();
|
||||
|
||||
$commandTester = $this->testSymfonyCommand('mautic:messages:send', ['--limit' => 2]);
|
||||
Assert::assertSame(0, $commandTester->getStatusCode());
|
||||
Assert::assertStringContainsString('Messages sent: 2', $commandTester->getDisplay());
|
||||
}
|
||||
|
||||
private function createMessageQueue(Email $email, Lead $lead, \DateTime $scheduledDate, \DateTime $datePublished): MessageQueue
|
||||
{
|
||||
$message = new MessageQueue();
|
||||
$message->setScheduledDate($scheduledDate);
|
||||
$message->setDatePublished($datePublished);
|
||||
$message->setChannel('email');
|
||||
$message->setChannelId($email->getId());
|
||||
$message->setLead($lead);
|
||||
$message->setPriority(MessageQueue::PRIORITY_NORMAL);
|
||||
$message->setMaxAttempts(3);
|
||||
$message->setAttempts(0);
|
||||
$message->setStatus(MessageQueue::STATUS_PENDING);
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function assertEmailStatCreated(Email $email, Lead $lead): void
|
||||
{
|
||||
/** @var StatRepository $emailStatRepository */
|
||||
$emailStatRepository = $this->em->getRepository(Stat::class);
|
||||
|
||||
/** @var Stat|null $emailStat */
|
||||
$emailStat = $emailStatRepository->findOneBy([
|
||||
'email' => $email->getId(),
|
||||
'lead' => $lead->getId(),
|
||||
]);
|
||||
|
||||
Assert::assertNotNull($emailStat, "Email stat not created for email ID {$email->getId()} and lead ID {$lead->getId()}");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Command;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
final class SendChannelBroadcastCommandTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testBroadcastCommand(): void
|
||||
{
|
||||
$commandTester = $this->testSymfonyCommand('mautic:broadcasts:send');
|
||||
Assert::assertSame(0, $commandTester->getStatusCode());
|
||||
}
|
||||
|
||||
public function testBroadcastCommandWithLimit(): void
|
||||
{
|
||||
$commandTester = $this->testSymfonyCommand('mautic:broadcasts:send', ['--limit' => 1]);
|
||||
Assert::assertSame(0, $commandTester->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Controller\Api;
|
||||
|
||||
use Mautic\ChannelBundle\Entity\Channel;
|
||||
use Mautic\ChannelBundle\Entity\Message;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
final class MessageApiControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testCreateMessage(): void
|
||||
{
|
||||
$payloadJson = <<<'JSON'
|
||||
{
|
||||
"name": "API message",
|
||||
"description": "Marketing message created via API functional test",
|
||||
"channels": {
|
||||
"email": {
|
||||
"channel": "email",
|
||||
"channelId": 12,
|
||||
"isEnabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
JSON;
|
||||
|
||||
$payloadArray = json_decode($payloadJson, true);
|
||||
|
||||
$this->client->request('POST', '/api/messages/new', $payloadArray);
|
||||
$responseJson = $this->client->getResponse()->getContent();
|
||||
self::assertResponseStatusCodeSame(201, $responseJson);
|
||||
$this->assertMessagePayload($payloadArray, json_decode($responseJson, true)['message'], $responseJson);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $payload
|
||||
* @param mixed[] $expectedResponsePayload
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('patchProvider')]
|
||||
public function testEditMessageWithPatch(array $payload, array $expectedResponsePayload): void
|
||||
{
|
||||
$channel = new Channel();
|
||||
$channel->setChannel('email');
|
||||
$channel->setChannelId(12);
|
||||
$channel->setIsEnabled(true);
|
||||
|
||||
$message = new Message();
|
||||
$message->setName('API message');
|
||||
$message->addChannel($channel);
|
||||
|
||||
$this->em->persist($channel);
|
||||
$this->em->persist($message);
|
||||
$this->em->flush();
|
||||
$this->em->detach($channel);
|
||||
$this->em->detach($message);
|
||||
|
||||
$patchPayload = ['id' => $message->getId()] + $payload;
|
||||
$this->client->request('PATCH', "/api/messages/{$message->getId()}/edit", $patchPayload);
|
||||
$responseJson = $this->client->getResponse()->getContent();
|
||||
self::assertResponseIsSuccessful($responseJson);
|
||||
$this->assertMessagePayload(
|
||||
['id' => $message->getId()] + $expectedResponsePayload,
|
||||
json_decode($responseJson, true)['message'],
|
||||
$responseJson
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: the ID is added to the payload automatically in the test.
|
||||
*
|
||||
* @return iterable<mixed[]>
|
||||
*/
|
||||
public static function patchProvider(): iterable
|
||||
{
|
||||
yield [
|
||||
[
|
||||
'name' => 'API message (updated)',
|
||||
],
|
||||
[
|
||||
'name' => 'API message (updated)',
|
||||
'description' => null,
|
||||
'channels' => [
|
||||
'email' => [
|
||||
'channel' => 'email',
|
||||
'channelId' => 12,
|
||||
'isEnabled' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
yield [
|
||||
[
|
||||
'description' => 'Description (updated)',
|
||||
'channels' => [
|
||||
'email' => [
|
||||
'channel' => 'email',
|
||||
'channelId' => 13,
|
||||
'isEnabled' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
'name' => 'API message',
|
||||
'description' => 'Description (updated)',
|
||||
'channels' => [
|
||||
'email' => [
|
||||
'channel' => 'email',
|
||||
'channelId' => 13,
|
||||
'isEnabled' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testEditMessagesWithPatch(): void
|
||||
{
|
||||
$channel1 = new Channel();
|
||||
$channel1->setChannel('email');
|
||||
$channel1->setChannelId(12);
|
||||
$channel1->setIsEnabled(true);
|
||||
|
||||
$message1 = new Message();
|
||||
$message1->setName('API message 1');
|
||||
$message1->addChannel($channel1);
|
||||
|
||||
$channel2 = new Channel();
|
||||
$channel2->setChannel('email');
|
||||
$channel2->setChannelId(13);
|
||||
$channel2->setIsEnabled(true);
|
||||
|
||||
$message2 = new Message();
|
||||
$message2->setName('API message 2');
|
||||
$message2->addChannel($channel2);
|
||||
|
||||
$this->em->persist($channel1);
|
||||
$this->em->persist($channel2);
|
||||
$this->em->persist($message1);
|
||||
$this->em->persist($message2);
|
||||
$this->em->flush();
|
||||
$this->em->detach($channel1);
|
||||
$this->em->detach($channel2);
|
||||
$this->em->detach($message1);
|
||||
$this->em->detach($message2);
|
||||
|
||||
$patchPayload = [
|
||||
['id' => $message1->getId(), 'name' => 'API message 1 (updated)'],
|
||||
['id' => $message2->getId(), 'channels' => ['email' => ['channelId' => 14, 'isEnabled' => false]]],
|
||||
];
|
||||
$this->client->request('PATCH', '/api/messages/batch/edit', $patchPayload);
|
||||
$responseJson = $this->client->getResponse()->getContent();
|
||||
self::assertResponseIsSuccessful($responseJson);
|
||||
$responseArray = json_decode($responseJson, true);
|
||||
$this->assertMessagePayload(
|
||||
[
|
||||
'id' => $message1->getId(),
|
||||
'name' => 'API message 1 (updated)',
|
||||
'description' => null,
|
||||
'channels' => [
|
||||
'email' => [
|
||||
'channel' => 'email',
|
||||
'channelId' => 12,
|
||||
'isEnabled' => true,
|
||||
],
|
||||
],
|
||||
],
|
||||
$responseArray['messages'][0],
|
||||
$responseJson
|
||||
);
|
||||
$this->assertMessagePayload(
|
||||
[
|
||||
'id' => $message2->getId(),
|
||||
'name' => 'API message 2',
|
||||
'description' => null,
|
||||
'channels' => [
|
||||
'email' => [
|
||||
'channel' => 'email',
|
||||
'channelId' => 14,
|
||||
'isEnabled' => false,
|
||||
],
|
||||
],
|
||||
],
|
||||
$responseArray['messages'][1],
|
||||
$responseJson
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $expectedPayload
|
||||
* @param mixed[] $actualPayload
|
||||
*/
|
||||
private function assertMessagePayload(array $expectedPayload, array $actualPayload, string $deliveredPayloadJson): void
|
||||
{
|
||||
Assert::assertSame($expectedPayload['name'], $actualPayload['name'], $deliveredPayloadJson);
|
||||
Assert::assertSame($expectedPayload['description'], $actualPayload['description'], $deliveredPayloadJson);
|
||||
Assert::assertCount(count($expectedPayload['channels']), $actualPayload['channels'], $deliveredPayloadJson);
|
||||
Assert::assertGreaterThan(0, $actualPayload['id'], $deliveredPayloadJson);
|
||||
|
||||
Assert::assertSame($expectedPayload['channels']['email']['channel'], $actualPayload['channels'][0]['channel'], $deliveredPayloadJson);
|
||||
Assert::assertSame($expectedPayload['channels']['email']['channelId'], $actualPayload['channels'][0]['channelId'], $deliveredPayloadJson);
|
||||
Assert::assertSame($expectedPayload['channels']['email']['isEnabled'], $actualPayload['channels'][0]['isEnabled'], $deliveredPayloadJson);
|
||||
Assert::assertGreaterThan(0, $actualPayload['channels'][0]['id'], $deliveredPayloadJson);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Controller;
|
||||
|
||||
use Mautic\ChannelBundle\Entity\Message;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
class MessageControllerFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testFormWithProject(): void
|
||||
{
|
||||
$message = new Message();
|
||||
$message->setName('Test message');
|
||||
$this->em->persist($message);
|
||||
|
||||
$project = new Project();
|
||||
$project->setName('Test Project');
|
||||
$this->em->persist($project);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$crawler = $this->client->request('GET', '/s/messages/edit/'.$message->getId());
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['message[projects]']->setValue((string) $project->getId());
|
||||
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$savedMessage = $this->em->find(Message::class, $message->getId());
|
||||
Assert::assertSame($project->getId(), $savedMessage->getProjects()->first()->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class MessageControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testMMUiWorkflow(): void
|
||||
{
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/messages/new');
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$form = $crawler->selectButton('Save & Close')->form([
|
||||
'message[name]' => 'Test message',
|
||||
'message[description]' => 'Test message description',
|
||||
]);
|
||||
|
||||
$this->client->submit($form);
|
||||
$this->assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Controller;
|
||||
|
||||
use Mautic\ChannelBundle\Entity\Channel;
|
||||
use Mautic\ChannelBundle\Entity\Message;
|
||||
use Mautic\ProjectBundle\Tests\Functional\AbstractProjectSearchTestCase;
|
||||
|
||||
final class MessageProjectSearchFunctionalTest extends AbstractProjectSearchTestCase
|
||||
{
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')]
|
||||
public function testProjectSearch(string $searchTerm, array $expectedEntities, array $unexpectedEntities): void
|
||||
{
|
||||
$projectOne = $this->createProject('Project One');
|
||||
$projectTwo = $this->createProject('Project Two');
|
||||
$projectThree = $this->createProject('Project Three');
|
||||
|
||||
$messageAlpha = $this->createMessage('Message Alpha');
|
||||
$messageBeta = $this->createMessage('Message Beta');
|
||||
$this->createMessage('Message Gamma');
|
||||
$this->createMessage('Message Delta');
|
||||
|
||||
$messageAlpha->addProject($projectOne);
|
||||
$messageAlpha->addProject($projectTwo);
|
||||
$messageBeta->addProject($projectTwo);
|
||||
$messageBeta->addProject($projectThree);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->searchAndAssert($searchTerm, $expectedEntities, $unexpectedEntities, ['/api/messages', '/s/messages']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<string, array{searchTerm: string, expectedEntities: array<string>, unexpectedEntities: array<string>}>
|
||||
*/
|
||||
public static function searchDataProvider(): \Generator
|
||||
{
|
||||
yield 'search by one project' => [
|
||||
'searchTerm' => 'project:"Project Two"',
|
||||
'expectedEntities' => ['Message Alpha', 'Message Beta'],
|
||||
'unexpectedEntities' => ['Message Gamma', 'Message Delta'],
|
||||
];
|
||||
|
||||
yield 'search by one project AND message name' => [
|
||||
'searchTerm' => 'project:"Project Two" AND Beta',
|
||||
'expectedEntities' => ['Message Beta'],
|
||||
'unexpectedEntities' => ['Message Alpha', 'Message Gamma', 'Message Delta'],
|
||||
];
|
||||
|
||||
yield 'search by one project OR message name' => [
|
||||
'searchTerm' => 'project:"Project Two" OR Gamma',
|
||||
'expectedEntities' => ['Message Alpha', 'Message Beta', 'Message Gamma'],
|
||||
'unexpectedEntities' => ['Message Delta'],
|
||||
];
|
||||
|
||||
yield 'search by NOT one project' => [
|
||||
'searchTerm' => '!project:"Project Two"',
|
||||
'expectedEntities' => ['Message Gamma', 'Message Delta'],
|
||||
'unexpectedEntities' => ['Message Alpha', 'Message Beta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with AND' => [
|
||||
'searchTerm' => 'project:"Project Two" AND project:"Project Three"',
|
||||
'expectedEntities' => ['Message Beta'],
|
||||
'unexpectedEntities' => ['Message Alpha', 'Message Gamma', 'Message Delta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with NOT AND' => [
|
||||
'searchTerm' => '!project:"Project Two" AND !project:"Project Three"',
|
||||
'expectedEntities' => ['Message Gamma', 'Message Delta'],
|
||||
'unexpectedEntities' => ['Message Alpha', 'Message Beta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with OR' => [
|
||||
'searchTerm' => 'project:"Project Two" OR project:"Project Three"',
|
||||
'expectedEntities' => ['Message Alpha', 'Message Beta'],
|
||||
'unexpectedEntities' => ['Message Gamma', 'Message Delta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with NOT OR' => [
|
||||
'searchTerm' => '!project:"Project Two" OR !project:"Project Three"',
|
||||
'expectedEntities' => ['Message Alpha', 'Message Gamma', 'Message Delta'],
|
||||
'unexpectedEntities' => ['Message Beta'],
|
||||
];
|
||||
}
|
||||
|
||||
private function createMessage(string $name): Message
|
||||
{
|
||||
$message = new Message();
|
||||
$message->setName($name);
|
||||
$message->addChannel((new Channel())
|
||||
->setChannel('email')
|
||||
->setMessage($message));
|
||||
$this->em->persist($message);
|
||||
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Entity;
|
||||
|
||||
use Mautic\CategoryBundle\Entity\Category;
|
||||
use Mautic\ChannelBundle\Entity\Message;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class MessageTest extends TestCase
|
||||
{
|
||||
public function testMessageUpdatesReflectsInChanges(): void
|
||||
{
|
||||
$category = new Category();
|
||||
$category->setTitle('New Category');
|
||||
$category->setAlias('category');
|
||||
$category->setBundle('bundle');
|
||||
|
||||
$message = new Message();
|
||||
$message->setName('New Message');
|
||||
$message->setDescription('random text string for description');
|
||||
$message->setCategory($category);
|
||||
$message->setPublishDown(new \DateTime());
|
||||
$message->setPublishUp(new \DateTime());
|
||||
|
||||
$this->assertIsArray($message->getChanges());
|
||||
$this->assertNotEmpty($message->getChanges());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Event;
|
||||
|
||||
use Mautic\ChannelBundle\Event\ChannelBroadcastEvent;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class ChannelBroadcastEventTest extends TestCase
|
||||
{
|
||||
private string $channel;
|
||||
|
||||
private int $channelId;
|
||||
|
||||
private OutputInterface $output;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->channel = 'email';
|
||||
$this->channelId = 1;
|
||||
$this->output = new BufferedOutput();
|
||||
}
|
||||
|
||||
public function testConstructorAndGetters(): void
|
||||
{
|
||||
$event = new ChannelBroadcastEvent($this->channel, $this->channelId, $this->output);
|
||||
|
||||
$this->assertSame($this->channel, $event->getChannel());
|
||||
$this->assertSame($this->channelId, $event->getId());
|
||||
$this->assertSame($this->output, $event->getOutput());
|
||||
}
|
||||
|
||||
public function testResults(): void
|
||||
{
|
||||
$event = new ChannelBroadcastEvent($this->channel, $this->channelId, $this->output);
|
||||
$successCount = 10;
|
||||
$failedCount = 2;
|
||||
$failedRecipientsByList = ['list1' => ['user1@example.com', 'user2@example.com']];
|
||||
|
||||
$event->setResults($this->channel, $successCount, $failedCount, $failedRecipientsByList);
|
||||
|
||||
$this->assertSame([
|
||||
$this->channel => [
|
||||
'success' => $successCount,
|
||||
'failed' => $failedCount,
|
||||
'failedRecipientsByList' => $failedRecipientsByList,
|
||||
],
|
||||
], $event->getResults());
|
||||
}
|
||||
|
||||
public function testCheckContext(): void
|
||||
{
|
||||
$event = new ChannelBroadcastEvent($this->channel, $this->channelId, $this->output);
|
||||
|
||||
$this->assertTrue($event->checkContext('email'));
|
||||
$this->assertFalse($event->checkContext('sms'));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,368 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\EventListener;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Entity\LeadEventLog;
|
||||
use Mautic\CampaignBundle\Event\CampaignExecutionEvent;
|
||||
use Mautic\CampaignBundle\Event\PendingEvent;
|
||||
use Mautic\CampaignBundle\EventCollector\Accessor\Event\ActionAccessor;
|
||||
use Mautic\CampaignBundle\EventCollector\EventCollector;
|
||||
use Mautic\CampaignBundle\Executioner\Dispatcher\ActionDispatcher;
|
||||
use Mautic\CampaignBundle\Executioner\Dispatcher\LegacyEventDispatcher;
|
||||
use Mautic\CampaignBundle\Executioner\Scheduler\EventScheduler;
|
||||
use Mautic\ChannelBundle\ChannelEvents;
|
||||
use Mautic\ChannelBundle\EventListener\CampaignSubscriber;
|
||||
use Mautic\ChannelBundle\Form\Type\MessageSendType;
|
||||
use Mautic\ChannelBundle\Model\MessageModel;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\EmailBundle\EmailEvents;
|
||||
use Mautic\EmailBundle\Form\Type\EmailListType;
|
||||
use Mautic\EmailBundle\Form\Type\EmailSendType;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Mautic\SmsBundle\Form\Type\SmsSendType;
|
||||
use Mautic\SmsBundle\SmsEvents;
|
||||
use Psr\Log\NullLogger;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class CampaignSubscriberTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private EventDispatcher $dispatcher;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|MessageModel
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $messageModel;
|
||||
|
||||
private ActionDispatcher $eventDispatcher;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|EventCollector
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $eventCollector;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|Translator
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $translator;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|EventScheduler
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $scheduler;
|
||||
|
||||
private LegacyEventDispatcher $legacyDispatcher;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->dispatcher = new EventDispatcher();
|
||||
|
||||
$this->messageModel = $this->createMock(MessageModel::class);
|
||||
|
||||
$this->messageModel->method('getChannels')
|
||||
->willReturn(
|
||||
[
|
||||
'email' => [
|
||||
'campaignAction' => 'email.send',
|
||||
'campaignDecisionsSupported' => [
|
||||
'email.open',
|
||||
'page.pagehit',
|
||||
'asset.download',
|
||||
'form.submit',
|
||||
],
|
||||
'lookupFormType' => EmailListType::class,
|
||||
],
|
||||
'sms' => [
|
||||
'campaignAction' => 'sms.send_text_sms',
|
||||
'campaignDecisionsSupported' => [
|
||||
'page.pagehit',
|
||||
'asset.download',
|
||||
'form.submit',
|
||||
],
|
||||
'lookupFormType' => 'sms_list',
|
||||
'repository' => \Mautic\SmsBundle\Entity\Sms::class,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->messageModel->method('getMessageChannels')
|
||||
->willReturn(
|
||||
[
|
||||
'email' => [
|
||||
'id' => 2,
|
||||
'channel' => 'email',
|
||||
'channel_id' => 2,
|
||||
'properties' => [],
|
||||
],
|
||||
'sms' => [
|
||||
'id' => 1,
|
||||
'channel' => 'sms',
|
||||
'channel_id' => 1,
|
||||
'properties' => [],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$this->scheduler = $this->createMock(EventScheduler::class);
|
||||
|
||||
$contactTracker = $this->createMock(ContactTracker::class);
|
||||
|
||||
$this->legacyDispatcher = new LegacyEventDispatcher(
|
||||
$this->dispatcher,
|
||||
$this->scheduler,
|
||||
new NullLogger(),
|
||||
$contactTracker
|
||||
);
|
||||
|
||||
$this->eventDispatcher = new ActionDispatcher(
|
||||
$this->dispatcher,
|
||||
new NullLogger(),
|
||||
$this->scheduler,
|
||||
$this->legacyDispatcher
|
||||
);
|
||||
|
||||
$this->eventCollector = $this->createMock(EventCollector::class);
|
||||
|
||||
$this->eventCollector->method('getEventConfig')
|
||||
->willReturnCallback(
|
||||
function (Event $event) {
|
||||
switch ($event->getType()) {
|
||||
case 'email.send':
|
||||
return new ActionAccessor(
|
||||
[
|
||||
'label' => 'mautic.email.campaign.event.send',
|
||||
'description' => 'mautic.email.campaign.event.send_descr',
|
||||
'batchEventName' => EmailEvents::ON_CAMPAIGN_BATCH_ACTION,
|
||||
'formType' => EmailSendType::class,
|
||||
'formTypeOptions' => ['update_select' => 'campaignevent_properties_email', 'with_email_types' => true],
|
||||
'formTheme' => 'MauticEmailBundle:FormTheme\EmailSendList',
|
||||
'channel' => 'email',
|
||||
'channelIdField' => 'email',
|
||||
]
|
||||
);
|
||||
|
||||
case 'sms.send_text_sms':
|
||||
return new ActionAccessor(
|
||||
[
|
||||
'label' => 'mautic.campaign.sms.send_text_sms',
|
||||
'description' => 'mautic.campaign.sms.send_text_sms.tooltip',
|
||||
'eventName' => SmsEvents::ON_CAMPAIGN_TRIGGER_ACTION,
|
||||
'formType' => SmsSendType::class,
|
||||
'formTypeOptions' => ['update_select' => 'campaignevent_properties_sms'],
|
||||
'formTheme' => 'MauticSmsBundle:FormTheme\SmsSendList',
|
||||
'timelineTemplate' => '@MauticSms/SubscribedEvents/Timeline/index.html.twig',
|
||||
'channel' => 'sms',
|
||||
'channelIdField' => 'sms',
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
$this->translator = $this->createMock(Translator::class);
|
||||
|
||||
$campaignSubscriber = new CampaignSubscriber(
|
||||
$this->messageModel,
|
||||
$this->eventDispatcher,
|
||||
$this->eventCollector,
|
||||
new NullLogger(),
|
||||
$this->translator
|
||||
);
|
||||
|
||||
$this->dispatcher->addSubscriber($campaignSubscriber);
|
||||
$this->dispatcher->addListener(EmailEvents::ON_CAMPAIGN_BATCH_ACTION, [$this, 'sendMarketingMessageEmail']);
|
||||
$this->dispatcher->addListener(SmsEvents::ON_CAMPAIGN_TRIGGER_ACTION, [$this, 'sendMarketingMessageSms']);
|
||||
}
|
||||
|
||||
public function testCorrectChannelIsUsed(): void
|
||||
{
|
||||
$event = $this->getEvent();
|
||||
$config = new ActionAccessor(
|
||||
[
|
||||
'label' => 'mautic.channel.message.send.marketing.message',
|
||||
'description' => 'mautic.channel.message.send.marketing.message.descr',
|
||||
'batchEventName' => ChannelEvents::ON_CAMPAIGN_BATCH_ACTION,
|
||||
'formType' => MessageSendType::class,
|
||||
'formTheme' => 'MauticChannelBundle:FormTheme\MessageSend',
|
||||
'channel' => 'channel.message',
|
||||
'channelIdField' => 'marketingMessage',
|
||||
'connectionRestrictions' => [
|
||||
'target' => [
|
||||
'decision' => [
|
||||
'email.open',
|
||||
'page.pagehit',
|
||||
'asset.download',
|
||||
'form.submit',
|
||||
],
|
||||
],
|
||||
],
|
||||
'timelineTemplate' => '@MauticChannel/SubscribedEvents/Timeline/index.html.twig',
|
||||
'timelineTemplateVars' => [
|
||||
'messageSettings' => [],
|
||||
],
|
||||
]
|
||||
);
|
||||
$logs = $this->getLogs();
|
||||
|
||||
$pendingEvent = new PendingEvent($config, $event, $logs);
|
||||
|
||||
$this->dispatcher->dispatch($pendingEvent, ChannelEvents::ON_CAMPAIGN_BATCH_ACTION);
|
||||
|
||||
$this->assertCount(0, $pendingEvent->getFailures());
|
||||
|
||||
$successful = $pendingEvent->getSuccessful();
|
||||
|
||||
// SMS should be noted as DNC
|
||||
$this->assertFalse(empty($successful->get(2)->getMetadata()['sms']['dnc']));
|
||||
|
||||
// Nothing recorded for success
|
||||
$this->assertTrue(empty($successful->get(1)->getMetadata()));
|
||||
}
|
||||
|
||||
public function sendMarketingMessageEmail(PendingEvent $event): void
|
||||
{
|
||||
$contacts = $event->getContacts();
|
||||
$logs = $event->getPending();
|
||||
$this->assertCount(1, $logs);
|
||||
|
||||
if (1 === $contacts->first()->getId()) {
|
||||
// Processing priority 1 for contact 1, let's fail this one so that SMS is used
|
||||
$event->fail($logs->first(), 'just because');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (2 === $contacts->first()->getId()) {
|
||||
// Processing priority 1 for contact 2 so let's pass it
|
||||
$event->pass($logs->first());
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* BC support for old campaign.
|
||||
*/
|
||||
public function sendMarketingMessageSms(CampaignExecutionEvent $event): void
|
||||
{
|
||||
$lead = $event->getLead();
|
||||
if (1 === $lead->getId()) {
|
||||
$event->setResult(true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (2 === $lead->getId()) {
|
||||
$this->fail('Lead ID 2 is unsubscribed from SMS so this shouldn not have happened.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Event|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private function getEvent()
|
||||
{
|
||||
$event = $this->getMockBuilder(Event::class)
|
||||
->onlyMethods(['getId'])
|
||||
->getMock();
|
||||
$event->method('getId')
|
||||
->willReturn(1);
|
||||
$event->setEventType(Event::TYPE_ACTION);
|
||||
$event->setType('message.send');
|
||||
$event->setChannel('channel.message');
|
||||
$event->setChannelId('1');
|
||||
$event->setProperties(
|
||||
[
|
||||
'canvasSettings' => [
|
||||
'droppedX' => '337',
|
||||
'droppedY' => '155',
|
||||
],
|
||||
'name' => '',
|
||||
'triggerMode' => 'immediate',
|
||||
'triggerDate' => null,
|
||||
'triggerInterval' => '1',
|
||||
'triggerIntervalUnit' => 'd',
|
||||
'anchor' => 'leadsource',
|
||||
'properties' => [
|
||||
'marketingMessage' => '1',
|
||||
],
|
||||
'type' => 'message.send',
|
||||
'eventType' => 'action',
|
||||
'anchorEventType' => 'source',
|
||||
'campaignId' => '1',
|
||||
'_token' => 'q7FpcDX7iye6fBuBzsqMvQWKqW75lcD77jSmuNAEDXg',
|
||||
'buttons' => [
|
||||
'save' => '',
|
||||
],
|
||||
'marketingMessage' => '1',
|
||||
]
|
||||
);
|
||||
$campaign = $this->createMock(Campaign::class);
|
||||
$campaign->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$event->setCampaign($campaign);
|
||||
|
||||
return $event;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ArrayCollection
|
||||
*/
|
||||
private function getLogs()
|
||||
{
|
||||
$lead = $this->createMock(Lead::class);
|
||||
$lead->method('getId')
|
||||
->willReturn(1);
|
||||
$lead->expects($this->once())
|
||||
->method('getChannelRules')
|
||||
->willReturn(
|
||||
[
|
||||
'sms' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
'email' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$log = $this->getMockBuilder(LeadEventLog::class)
|
||||
->onlyMethods(['getLead', 'getId'])
|
||||
->getMock();
|
||||
$log->method('getLead')
|
||||
->willReturn($lead);
|
||||
$log->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$lead2 = $this->createMock(Lead::class);
|
||||
$lead2->method('getId')
|
||||
->willReturn(2);
|
||||
$lead2->expects($this->once())
|
||||
->method('getChannelRules')
|
||||
->willReturn(
|
||||
[
|
||||
'email' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
'sms' => [
|
||||
'dnc' => DoNotContact::UNSUBSCRIBED,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$log2 = $this->getMockBuilder(LeadEventLog::class)
|
||||
->onlyMethods(['getLead', 'getId'])
|
||||
->getMock();
|
||||
$log2->method('getLead')
|
||||
->willReturn($lead2);
|
||||
$log2->method('getId')
|
||||
->willReturn(2);
|
||||
|
||||
return new ArrayCollection([1 => $log, 2 => $log2]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Model;
|
||||
|
||||
use Mautic\ChannelBundle\Model\ChannelActionModel;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact as DNC;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\DoNotContact;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ChannelActionModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private \PHPUnit\Framework\MockObject\MockObject $contactMock5;
|
||||
|
||||
private \PHPUnit\Framework\MockObject\MockObject $contactMock6;
|
||||
|
||||
private \PHPUnit\Framework\MockObject\MockObject $contactModelMock;
|
||||
|
||||
private \PHPUnit\Framework\MockObject\MockObject $doNotContactMock;
|
||||
|
||||
private \PHPUnit\Framework\MockObject\MockObject $translatorMock;
|
||||
|
||||
private ChannelActionModel $actionModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->contactMock5 = $this->createMock(Lead::class);
|
||||
$this->contactMock6 = $this->createMock(Lead::class);
|
||||
$this->contactModelMock = $this->createMock(LeadModel::class);
|
||||
$this->doNotContactMock = $this->createMock(DoNotContact::class);
|
||||
$this->translatorMock = $this->createMock(TranslatorInterface::class);
|
||||
$this->actionModel = new ChannelActionModel(
|
||||
$this->contactModelMock,
|
||||
$this->doNotContactMock,
|
||||
$this->translatorMock
|
||||
);
|
||||
|
||||
$this->contactMock5->method('getId')->willReturn(5);
|
||||
}
|
||||
|
||||
public function testUpdateEntityAccess(): void
|
||||
{
|
||||
$contacts = [5, 6];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5, $this->contactMock6]);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('canEditContact')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
$this->contactModelMock->expects($this->never())
|
||||
->method('getContactChannels');
|
||||
|
||||
$this->actionModel->update($contacts, []);
|
||||
}
|
||||
|
||||
public function testSubscribeContactToEmailChannel(): void
|
||||
{
|
||||
$contacts = [5];
|
||||
$subscribedChannels = ['email', 'sms']; // Subscribe contact to these channels
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5]);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('canEditContact')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(true);
|
||||
|
||||
// Contact is already subscribed to the SMS channel but not to email
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getContactChannels')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(['sms' => 'sms']);
|
||||
|
||||
$this->doNotContactMock->expects($this->once())
|
||||
->method('isContactable')
|
||||
->with($this->contactMock5, 'email')
|
||||
->willReturn(DNC::IS_CONTACTABLE);
|
||||
|
||||
$this->doNotContactMock->expects($this->once())
|
||||
->method('removeDncForContact')
|
||||
->with(5, 'email');
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getPreferenceChannels')
|
||||
->willReturn(['Email' => 'email', 'Text Message' => 'sms']);
|
||||
|
||||
$this->doNotContactMock->expects($this->never())
|
||||
->method('addDncForContact');
|
||||
|
||||
$this->actionModel->update($contacts, $subscribedChannels);
|
||||
}
|
||||
|
||||
public function testSubscribeContactWhoUnsubscribedToEmailChannel(): void
|
||||
{
|
||||
$contacts = [5];
|
||||
$subscribedChannels = ['email', 'sms']; // Subscribe contact to these channels
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5]);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('canEditContact')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(true);
|
||||
|
||||
// Contact is already subscribed to the SMS channel but not to email
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getContactChannels')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(['sms' => 'sms']);
|
||||
|
||||
$this->doNotContactMock->expects($this->once())
|
||||
->method('isContactable')
|
||||
->with($this->contactMock5, 'email')
|
||||
->willReturn(DNC::UNSUBSCRIBED);
|
||||
|
||||
$this->doNotContactMock->expects($this->never())
|
||||
->method('removeDncForContact');
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getPreferenceChannels')
|
||||
->willReturn(['Email' => 'email', 'Text Message' => 'sms']);
|
||||
|
||||
$this->doNotContactMock->expects($this->never())
|
||||
->method('addDncForContact');
|
||||
|
||||
$this->actionModel->update($contacts, $subscribedChannels);
|
||||
}
|
||||
|
||||
public function testUnsubscribeContactFromSmsChannel(): void
|
||||
{
|
||||
$contacts = [5];
|
||||
$subscribedChannels = []; // Unsubscribe contact from missing
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5]);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('canEditContact')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(true);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getContactChannels')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(['sms' => 'sms']);
|
||||
|
||||
$this->doNotContactMock->expects($this->never())
|
||||
->method('isContactable');
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getPreferenceChannels')
|
||||
->willReturn(['Email' => 'email', 'Text Message' => 'sms']);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->doNotContactMock->expects($matcher)
|
||||
->method('addDncForContact')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame(5, $parameters[0]);
|
||||
$this->assertSame('email', $parameters[1]);
|
||||
$this->assertSame(DNC::MANUAL, $parameters[2]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame(5, $parameters[0]);
|
||||
$this->assertSame('sms', $parameters[1]);
|
||||
$this->assertSame(DNC::MANUAL, $parameters[2]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->actionModel->update($contacts, $subscribedChannels);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Model;
|
||||
|
||||
use Doctrine\Common\Collections\AbstractLazyCollection;
|
||||
use Mautic\ChannelBundle\Model\FrequencyActionModel;
|
||||
use Mautic\LeadBundle\Entity\FrequencyRule;
|
||||
use Mautic\LeadBundle\Entity\FrequencyRuleRepository;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
|
||||
class FrequencyActionModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var MockObject|Lead
|
||||
*/
|
||||
private MockObject $contactMock5;
|
||||
|
||||
/**
|
||||
* @var MockObject|LeadModel
|
||||
*/
|
||||
private MockObject $contactModelMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|FrequencyRuleRepository
|
||||
*/
|
||||
private MockObject $frequencyRepoMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|FrequencyRule
|
||||
*/
|
||||
private MockObject $frequencyRuleEmailMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|FrequencyRule
|
||||
*/
|
||||
private MockObject $frequencyRuleSmsMock;
|
||||
|
||||
private FrequencyActionModel $actionModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->contactMock5 = $this->createMock(Lead::class);
|
||||
$this->contactModelMock = $this->createMock(LeadModel::class);
|
||||
$this->frequencyRepoMock = $this->createMock(FrequencyRuleRepository::class);
|
||||
$this->frequencyRuleEmailMock = $this->createMock(FrequencyRule::class);
|
||||
$this->frequencyRuleSmsMock = $this->createMock(FrequencyRule::class);
|
||||
$collectionMock = $this->createMock(AbstractLazyCollection::class);
|
||||
$this->actionModel = new FrequencyActionModel(
|
||||
$this->contactModelMock,
|
||||
$this->frequencyRepoMock
|
||||
);
|
||||
|
||||
$collectionMock->method('toArray')
|
||||
->willReturn([
|
||||
'email' => $this->frequencyRuleEmailMock,
|
||||
'sms' => $this->frequencyRuleSmsMock,
|
||||
]);
|
||||
|
||||
$this->contactMock5->method('getFrequencyRules')->willReturn($collectionMock);
|
||||
}
|
||||
|
||||
public function testUpdateWhenEntityAccess(): void
|
||||
{
|
||||
$contacts = [5];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5]);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('canEditContact')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(false);
|
||||
|
||||
$this->contactModelMock->expects($this->never())
|
||||
->method('getPreferenceChannels');
|
||||
|
||||
$this->actionModel->update($contacts, [], '');
|
||||
}
|
||||
|
||||
public function testUpdate(): void
|
||||
{
|
||||
$contacts = [5];
|
||||
$params = [
|
||||
'subscribed_channels' => ['email', 'sms'],
|
||||
'frequency_number_email' => '2',
|
||||
'frequency_time_email' => 'WEEK',
|
||||
'preferred_channel' => 'email',
|
||||
'contact_pause_start_date_email' => '2018-05-13',
|
||||
'contact_pause_end_date_email' => '2018-05-26',
|
||||
'frequency_number_sms' => '',
|
||||
'frequency_time_sms' => '',
|
||||
'contact_pause_start_date_sms' => '',
|
||||
'contact_pause_end_date_sms' => '',
|
||||
];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5]);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('canEditContact')
|
||||
->with($this->contactMock5)
|
||||
->willReturn(true);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getPreferenceChannels')
|
||||
->willReturn([
|
||||
'Email' => 'email',
|
||||
'Text Message' => 'sms',
|
||||
]);
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setChannel')
|
||||
->with('email');
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setLead')
|
||||
->with($this->contactMock5);
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setDateAdded');
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setFrequencyNumber')
|
||||
->with('2');
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setFrequencyTime')
|
||||
->with('WEEK');
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setPauseFromDate')
|
||||
->with(new \DateTime('2018-05-13T00:00:00.000000+0000'));
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setPauseToDate')
|
||||
->with(new \DateTime('2018-05-26T00:00:00.000000+0000'));
|
||||
|
||||
$this->frequencyRuleEmailMock->expects($this->once())
|
||||
->method('setPreferredChannel')
|
||||
->with(true);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactMock5->expects($matcher)
|
||||
->method('addFrequencyRule')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals($this->frequencyRuleEmailMock, $parameters[0]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals($this->frequencyRuleEmailMock, $parameters[0]);
|
||||
}
|
||||
});
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->frequencyRepoMock->expects($matcher)
|
||||
->method('saveEntity')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->frequencyRuleEmailMock, $parameters[0]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->frequencyRuleSmsMock, $parameters[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->once())
|
||||
->method('setChannel')
|
||||
->with('sms');
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->once())
|
||||
->method('setLead')
|
||||
->with($this->contactMock5);
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->once())
|
||||
->method('setDateAdded');
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->once())
|
||||
->method('setFrequencyNumber')
|
||||
->with(null);
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->once())
|
||||
->method('setFrequencyTime')
|
||||
->with(null);
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->never())
|
||||
->method('setPauseFromDate');
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->never())
|
||||
->method('setPauseToDate');
|
||||
|
||||
$this->frequencyRuleSmsMock->expects($this->once())
|
||||
->method('setPreferredChannel')
|
||||
->with(false);
|
||||
|
||||
$this->actionModel->update($contacts, $params, 'email');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\ChannelBundle\Entity\MessageQueue;
|
||||
use Mautic\ChannelBundle\Entity\MessageQueueRepository;
|
||||
use Mautic\ChannelBundle\Model\MessageQueueModel;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class MessageQueueModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const DATE = '2019-07-07 15:00:00';
|
||||
|
||||
/**
|
||||
* @var MessageQueueModel
|
||||
*/
|
||||
protected $messageQueue;
|
||||
|
||||
/**
|
||||
* @var MessageQueue
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
/** @var MockObject|LeadModel */
|
||||
protected $leadModel;
|
||||
|
||||
/** @var MockObject|CompanyModel */
|
||||
protected $companyModel;
|
||||
|
||||
/** @var MockObject|EntityManagerInterface */
|
||||
protected $entityManager;
|
||||
|
||||
/** @var MockObject|MessageQueueRepository */
|
||||
protected $messageQueueRepository;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->leadModel = $this->createMock(LeadModel::class);
|
||||
$this->companyModel = $this->createMock(CompanyModel::class);
|
||||
$this->entityManager = $this->createMock(EntityManagerInterface::class);
|
||||
$this->messageQueueRepository = $this->createMock(MessageQueueRepository::class);
|
||||
$coreHelper = $this->createMock(CoreParametersHelper::class);
|
||||
|
||||
$this->messageQueue = new MessageQueueModel(
|
||||
$this->leadModel,
|
||||
$this->companyModel,
|
||||
$coreHelper,
|
||||
$this->entityManager,
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class)
|
||||
);
|
||||
|
||||
$this->entityManager->method('getRepository')->willReturn($this->messageQueueRepository);
|
||||
|
||||
$message = new MessageQueue();
|
||||
$scheduleDate = new \DateTime(self::DATE);
|
||||
$message->setScheduledDate($scheduleDate);
|
||||
|
||||
$this->message = $message;
|
||||
}
|
||||
|
||||
public function testRescheduleMessageIntervalDay(): void
|
||||
{
|
||||
$interval = new \DateInterval('P2D');
|
||||
$this->prepareRescheduleMessageIntervalTest($interval);
|
||||
}
|
||||
|
||||
public function testRescheduleMessageIntervalWeek(): void
|
||||
{
|
||||
$interval = new \DateInterval('P4W');
|
||||
$this->prepareRescheduleMessageIntervalTest($interval);
|
||||
}
|
||||
|
||||
public function testRescheduleMessageIntervalMonth(): void
|
||||
{
|
||||
$interval = new \DateInterval('P8M');
|
||||
$this->prepareRescheduleMessageIntervalTest($interval);
|
||||
}
|
||||
|
||||
public function testRescheduleMessageNoInterval(): void
|
||||
{
|
||||
$interval = new \DateInterval('PT0S');
|
||||
$this->prepareRescheduleMessageIntervalTest($interval);
|
||||
}
|
||||
|
||||
protected function prepareRescheduleMessageIntervalTest(\DateInterval $interval)
|
||||
{
|
||||
$oldScheduleDate = $this->message->getScheduledDate();
|
||||
$this->messageQueue->reschedule($this->message, $interval);
|
||||
$scheduleDate = $this->message->getScheduledDate();
|
||||
/** @var \DateTime $oldScheduleDate */
|
||||
$oldScheduleDate->add($interval);
|
||||
|
||||
$this->assertEquals($oldScheduleDate, $scheduleDate);
|
||||
$this->assertNotSame($oldScheduleDate, $scheduleDate);
|
||||
}
|
||||
|
||||
public function testSendMessagesWithNullEvent(): void
|
||||
{
|
||||
$queue = $this->message;
|
||||
$lead = new Lead();
|
||||
$lead->setId(1);
|
||||
$queue->setLead($lead);
|
||||
|
||||
$contactData = [
|
||||
1 => [
|
||||
'firstname' => 'John',
|
||||
'email' => 'john.doe@example.com',
|
||||
],
|
||||
];
|
||||
|
||||
$leadRepository = $this->createMock(LeadRepository::class);
|
||||
$this->leadModel->method('getRepository')->willReturn($leadRepository);
|
||||
$leadRepository->method('getContacts')->willReturn($contactData);
|
||||
|
||||
$this->entityManager->expects($this->exactly(1))
|
||||
->method('detach');
|
||||
|
||||
$this->messageQueueRepository->method('getQueuedMessages')
|
||||
->willReturn([$queue]);
|
||||
|
||||
$this->messageQueue->sendMessages('email', 1);
|
||||
}
|
||||
|
||||
public function testProcessMessageQueueLeadFieldsShouldNotContainCompany(): void
|
||||
{
|
||||
$queue = $this->message;
|
||||
|
||||
$lead = new Lead();
|
||||
$lead->setId(1);
|
||||
$queue->setLead($lead);
|
||||
|
||||
$contactData = [
|
||||
1 => [
|
||||
'firstname' => 'John',
|
||||
'email' => 'john.doe@example.com',
|
||||
],
|
||||
];
|
||||
|
||||
$leadRepository = $this->createMock(LeadRepository::class);
|
||||
$this->leadModel->method('getRepository')->willReturn($leadRepository);
|
||||
$leadRepository->method('getContacts')->willReturn($contactData);
|
||||
|
||||
$this->messageQueue->processMessageQueue($queue);
|
||||
$this->assertArrayNotHasKey('companies', $queue->getLead()->getFields());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\PreferenceBuilder;
|
||||
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Entity\LeadEventLog;
|
||||
use Mautic\ChannelBundle\PreferenceBuilder\ChannelPreferences;
|
||||
|
||||
class ChannelPreferencesTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testLogsAreOrganizedByPriority(): void
|
||||
{
|
||||
$campaign = new Campaign();
|
||||
$event = new Event();
|
||||
$event->setCampaign($campaign);
|
||||
|
||||
$channelPreferences = $this->getChannelPreference('email', $event);
|
||||
|
||||
$log1 = new LeadEventLog();
|
||||
$log1->setEvent($event);
|
||||
$log1->setCampaign($campaign);
|
||||
$log1->setMetadata(['log' => 1]);
|
||||
$channelPreferences->addLog($log1, 1);
|
||||
|
||||
$log2 = new LeadEventLog();
|
||||
$log2->setEvent($event);
|
||||
$log2->setCampaign($campaign);
|
||||
$log2->setMetadata(['log' => 2]);
|
||||
$channelPreferences->addLog($log2, 2);
|
||||
|
||||
$organized = $channelPreferences->getLogsByPriority(1);
|
||||
$this->assertEquals($organized->first()->getMetadata()['log'], 1);
|
||||
|
||||
$organized = $channelPreferences->getLogsByPriority(2);
|
||||
$this->assertEquals($organized->first()->getMetadata()['log'], 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ChannelPreferences
|
||||
*/
|
||||
private function getChannelPreference($channel, Event $event)
|
||||
{
|
||||
return new ChannelPreferences($event);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\ChannelBundle\Tests\PreferenceBuilder;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Mautic\CampaignBundle\Entity\Event;
|
||||
use Mautic\CampaignBundle\Entity\LeadEventLog;
|
||||
use Mautic\ChannelBundle\PreferenceBuilder\ChannelPreferences;
|
||||
use Mautic\ChannelBundle\PreferenceBuilder\PreferenceBuilder;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Psr\Log\NullLogger;
|
||||
|
||||
class PreferenceBuilderTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
public function testChannelsArePrioritized(): void
|
||||
{
|
||||
$lead = $this->createMock(Lead::class);
|
||||
$lead->expects($this->once())
|
||||
->method('getChannelRules')
|
||||
->willReturn(
|
||||
[
|
||||
'sms' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
'email' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$log = $this->createMock(LeadEventLog::class);
|
||||
$log->method('getLead')
|
||||
->willReturn($lead);
|
||||
$log->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$lead2 = $this->createMock(Lead::class);
|
||||
$lead2->expects($this->once())
|
||||
->method('getChannelRules')
|
||||
->willReturn(
|
||||
[
|
||||
'email' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
'sms' => [
|
||||
'dnc' => DoNotContact::UNSUBSCRIBED,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$log2 = $this->createMock(LeadEventLog::class);
|
||||
$log2->method('getLead')
|
||||
->willReturn($lead2);
|
||||
$log2->method('getId')
|
||||
->willReturn(2);
|
||||
|
||||
$logs = new ArrayCollection([$log, $log2]);
|
||||
|
||||
$event = new Event();
|
||||
|
||||
$builder = new PreferenceBuilder($logs, $event, ['email' => [], 'sms' => [], 'push' => []], new NullLogger());
|
||||
|
||||
$preferences = $builder->getChannelPreferences();
|
||||
|
||||
$this->assertCount(3, $preferences);
|
||||
$this->assertTrue(isset($preferences['email']));
|
||||
$this->assertTrue(isset($preferences['sms']));
|
||||
$this->assertTrue(isset($preferences['push']));
|
||||
|
||||
/** @var ChannelPreferences $emailLogs */
|
||||
$email = $preferences['email'];
|
||||
|
||||
// First priority
|
||||
$emailLogs = $email->getLogsByPriority(1);
|
||||
$this->assertCount(1, $emailLogs);
|
||||
$this->assertEquals(2, $emailLogs->first()->getId());
|
||||
|
||||
// Second priority
|
||||
$emailLogs = $email->getLogsByPriority(2);
|
||||
$this->assertCount(1, $emailLogs);
|
||||
$this->assertEquals(1, $emailLogs->first()->getId());
|
||||
|
||||
// First priority for SMS which should just be one
|
||||
/** @var ChannelPreferences $smsLogs */
|
||||
$sms = $preferences['sms'];
|
||||
$smsLogs = $sms->getLogsByPriority(1);
|
||||
$this->assertCount(1, $smsLogs);
|
||||
$this->assertEquals(1, $smsLogs->first()->getId());
|
||||
|
||||
// None for second priority because of DNC
|
||||
$smsLogs = $sms->getLogsByPriority(2);
|
||||
$this->assertCount(0, $smsLogs);
|
||||
|
||||
// No one had push enabled but it should be defined
|
||||
$push = $preferences['push'];
|
||||
$pushLogs = $push->getLogsByPriority(1);
|
||||
$this->assertCount(0, $pushLogs);
|
||||
}
|
||||
|
||||
public function testLogIsRemovedFromAllChannels(): void
|
||||
{
|
||||
$lead = $this->createMock(Lead::class);
|
||||
$lead->expects($this->once())
|
||||
->method('getChannelRules')
|
||||
->willReturn(
|
||||
[
|
||||
'sms' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
'email' => [
|
||||
'dnc' => DoNotContact::IS_CONTACTABLE,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$log = $this->createMock(LeadEventLog::class);
|
||||
$log->method('getLead')
|
||||
->willReturn($lead);
|
||||
$log->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$logs = new ArrayCollection([$log]);
|
||||
|
||||
$event = new Event();
|
||||
$builder = new PreferenceBuilder($logs, $event, ['email' => [], 'sms' => [], 'push' => []], new NullLogger());
|
||||
|
||||
$preferences = $builder->getChannelPreferences();
|
||||
/** @var ChannelPreferences $sms */
|
||||
$sms = $preferences['sms'];
|
||||
$smsLogs = $sms->getLogsByPriority(1);
|
||||
$this->assertCount(1, $smsLogs);
|
||||
|
||||
/** @var ChannelPreferences $email */
|
||||
$email = $preferences['email'];
|
||||
$emailLogs = $email->getLogsByPriority(2);
|
||||
$this->assertCount(1, $emailLogs);
|
||||
|
||||
$builder->removeLogFromAllChannels($log);
|
||||
|
||||
$preferences = $builder->getChannelPreferences();
|
||||
/** @var ChannelPreferences $sms */
|
||||
$sms = $preferences['sms'];
|
||||
$smsLogs = $sms->getLogsByPriority(1);
|
||||
$this->assertCount(0, $smsLogs);
|
||||
|
||||
/** @var ChannelPreferences $email */
|
||||
$email = $preferences['email'];
|
||||
$emailLogs = $email->getLogsByPriority(2);
|
||||
$this->assertCount(0, $emailLogs);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user