Initial commit: CloudOps infrastructure platform

This commit is contained in:
root
2026-04-09 19:58:57 +02:00
commit 1166a52f26
7762 changed files with 839452 additions and 0 deletions

View File

@@ -0,0 +1,195 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Mautic\CampaignBundle\Entity\Campaign;
use Mautic\CampaignBundle\Entity\CampaignRepository;
use Mautic\CampaignBundle\Entity\Event;
use Mautic\CampaignBundle\Entity\Lead as CampaignLead;
use Mautic\CampaignBundle\Entity\LeadEventLog;
use Mautic\CampaignBundle\Entity\Result\CountResult;
use Mautic\CampaignBundle\Executioner\ContactFinder\Limiter\ContactLimiter;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\Assert;
class CampaignRepositoryFunctionalTest extends MauticMysqlTestCase
{
private CampaignRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = self::getContainer()->get('mautic.campaign.repository.campaign');
}
public function testGetCountsForPendingContactsWithEmptyData(): void
{
$result = $this->repository->getCountsForPendingContacts(
1,
[1, 2, 3],
new ContactLimiter(100, null, null, null, [1, 2, 3])
);
Assert::assertEquals(
new CountResult(0, 0, 0),
$result,
'There should not be any match as there are no campaign/lead records.'
);
}
public function testGetCountsForPendingContactsWithoutEventLogs(): void
{
$campaign = $this->createCampaign();
$eventOne = $this->createEvent($campaign);
$eventTwo = $this->createEvent($campaign);
$eventThree = $this->createEvent($campaign);
$leadOne = $this->createLead($campaign);
$leadTwo = $this->createLead($campaign);
$leadThree = $this->createLead($campaign);
$this->em->flush();
$result = $this->repository->getCountsForPendingContacts(
$campaign->getId(),
[$eventOne->getId(), $eventTwo->getId(), $eventThree->getId()],
new ContactLimiter(100, null, null, null, [$leadOne->getId(), $leadTwo->getId(), $leadThree->getId()])
);
Assert::assertEquals(
new CountResult(3, $leadOne->getId(), $leadThree->getId()),
$result,
'All three leads should match as none of them have any event logs.'
);
}
public function testGetCountsForPendingContactsWithEventLogs(): void
{
$campaign = $this->createCampaign();
$logOne = $this->createEventLog($campaign);
$leadOne = $logOne->getLead();
$eventOne = $logOne->getEvent();
$logTwo = $this->createEventLog($campaign);
$leadTwo = $logTwo->getLead();
$eventTwo = $logTwo->getEvent();
$leadThree = $this->createLead($campaign);
$eventThree = $this->createEvent($campaign);
$this->em->flush();
$result = $this->repository->getCountsForPendingContacts(
$campaign->getId(),
[$eventOne->getId(), $eventTwo->getId(), $eventThree->getId()],
new ContactLimiter(100, null, null, null, [$leadOne->getId(), $leadTwo->getId(), $leadThree->getId()])
);
Assert::assertEquals(
new CountResult(1, $leadThree->getId(), $leadThree->getId()),
$result,
'Only lead three should match as it is the only one who does not have any event log.'
);
}
public function testGetCountsForPendingContactsWithEventLogsWithNonMatchingRotations(): void
{
$campaign = $this->createCampaign();
$logOne = $this->createEventLog($campaign);
$leadOne = $logOne->getLead();
$eventOne = $logOne->getEvent();
$logTwo = $this->createEventLog($campaign, $campaignLeadTwo);
$leadTwo = $logTwo->getLead();
$eventTwo = $logTwo->getEvent();
$logThree = $this->createEventLog($campaign);
$leadThree = $logThree->getLead();
$eventThree = $logThree->getEvent();
$campaignLeadTwo->setRotation($logTwo->getRotation() + 1);
$this->em->flush();
$result = $this->repository->getCountsForPendingContacts(
$campaign->getId(),
[$eventOne->getId(), $eventTwo->getId(), $eventThree->getId()],
new ContactLimiter(100, null, null, null, [$leadOne->getId(), $leadTwo->getId(), $leadThree->getId()])
);
Assert::assertEquals(
new CountResult(1, $leadTwo->getId(), $leadTwo->getId()),
$result,
'Only lead two should match as it is the only one who has a non-matching rotation.'
);
}
public function testGetCampaignPublishAndVersionData(): void
{
$campaign = $this->createCampaign();
$this->em->flush();
$result = $this->repository->getCampaignPublishAndVersionData($campaign->getId());
Assert::assertIsArray($result);
Assert::assertArrayHasKey('is_published', $result);
Assert::assertArrayHasKey('version', $result);
Assert::assertEquals('1', $result['is_published']);
// Version should be a string representation of an integer
Assert::assertIsString($result['version']);
Assert::assertGreaterThanOrEqual('1', $result['version']);
}
public function testGetCampaignPublishAndVersionDataWithNonExistentCampaign(): void
{
$nonExistentId = 99999;
$result = $this->repository->getCampaignPublishAndVersionData($nonExistentId);
Assert::assertEquals([], $result);
}
private function createLead(Campaign $campaign, ?CampaignLead &$campaignLead = null): Lead // @phpstan-ignore parameterByRef.unusedType
{
$lead = new Lead();
$this->em->persist($lead);
$campaignLead = new CampaignLead();
$campaignLead->setCampaign($campaign);
$campaignLead->setLead($lead);
$campaignLead->setDateAdded(new \DateTime());
$this->em->persist($campaignLead);
return $lead;
}
private function createCampaign(): Campaign
{
$campaign = new Campaign();
$campaign->setName('Test campaign');
$campaign->setIsPublished(true);
$this->em->persist($campaign);
return $campaign;
}
private function createEvent(Campaign $campaign): Event
{
$event = new Event();
$event->setName('Test event');
$event->setCampaign($campaign);
$event->setType('lead.changepoints');
$event->setEventType(Event::TYPE_ACTION);
$this->em->persist($event);
return $event;
}
private function createEventLog(Campaign $campaign, ?CampaignLead &$campaignLead = null): LeadEventLog
{
$event = $this->createEvent($campaign);
$lead = $this->createLead($campaign, $campaignLead);
$leadEventLog = new LeadEventLog();
$leadEventLog->setLead($lead);
$leadEventLog->setEvent($event);
$leadEventLog->setTriggerDate(new \DateTime());
$this->em->persist($leadEventLog);
return $leadEventLog;
}
}

View File

@@ -0,0 +1,157 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Doctrine\DBAL\Query\QueryBuilder as DbalQueryBuilder;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Mautic\CampaignBundle\Entity\Campaign;
use Mautic\CampaignBundle\Entity\CampaignRepository;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
class CampaignRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
/**
* @var MockObject&QueryBuilder
*/
private MockObject $queryBuilder;
private CampaignRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->queryBuilder = $this->getMockBuilder(QueryBuilder::class)
->disableOriginalConstructor()
->onlyMethods(['select', 'from', 'where', 'setParameter', 'andWhere', 'getQuery', 'getRootAliases'])
->getMock();
$this->repository = $this->configureRepository(Campaign::class);
$this->entityManager->method('createQueryBuilder')->willReturn($this->queryBuilder);
$this->connection->method('createQueryBuilder')->willReturnCallback(fn () => new DbalQueryBuilder($this->connection));
$translator = $this->createMock(TranslatorInterface::class);
$translator->method('trans')->willReturnCallback(fn ($id) => match ($id) {
'mautic.campaign.campaign.searchcommand.isexpired' => 'is:expired',
'mautic.campaign.campaign.searchcommand.ispending' => 'is:pending',
default => $id,
});
$this->repository->setTranslator($translator);
}
public function testFetchEmailIdsById(): void
{
$id = 2;
$queryResult = [
1 => ['channelId' => 1],
2 => ['channelId' => 2],
];
$expectedResult = [1, 2];
$this->entityManager
->method('createQueryBuilder')
->willReturn($this->queryBuilder);
$this->queryBuilder->expects(self::once())
->method('select')
->with('e.channelId')
->willReturn($this->queryBuilder);
$this->queryBuilder->expects(self::once())
->method('from')
->with(Campaign::class, $this->repository->getTableAlias(), $this->repository->getTableAlias().'.id')
->willReturn($this->queryBuilder);
$this->queryBuilder->expects(self::once())
->method('where')
->with($this->repository->getTableAlias().'.id = :id')
->willReturn($this->queryBuilder);
$this->queryBuilder->expects(self::once())
->method('setParameter')
->with('id', $id)
->willReturn($this->queryBuilder);
$this->queryBuilder->method('getRootAliases')
->willReturn(['e']);
$this->queryBuilder->expects(self::once())
->method('andWhere')
->with('e.channelId IS NOT NULL')
->willReturn($this->queryBuilder);
$query = $this->getMockBuilder(Query::class)
->disableOriginalConstructor()
->onlyMethods(['setHydrationMode', 'getResult'])
->getMock();
$query->expects(self::once())
->method('setHydrationMode')
->with(Query::HYDRATE_ARRAY)
->willReturn($query);
$this->queryBuilder->expects(self::once())
->method('getQuery')
->willReturn($query);
$query->expects(self::once())
->method('getResult')
->willReturn($queryResult);
$result = $this->repository->fetchEmailIdsById($id);
$this->assertEquals($expectedResult, $result);
}
public function testAddSearchCommandWhereClauseHandlesExpirationFilters(): void
{
$qb = $this->connection->createQueryBuilder();
$filter = (object) ['command' => 'is:expired', 'string' => '', 'not' => false, 'strict' => false];
$method = new \ReflectionMethod(CampaignRepository::class, 'addSearchCommandWhereClause');
$method->setAccessible(true);
[$expr, $params] = $method->invoke($this->repository, $qb, $filter);
self::assertSame(
'(c.isPublished = :par1) AND (c.publishDown IS NOT NULL) AND (c.publishDown <> \'\') AND (c.publishDown < CURRENT_TIMESTAMP())',
(string) $expr
);
self::assertSame(['par1' => true], $params);
}
public function testAddSearchCommandWhereClauseHandlesPendingFilters(): void
{
$qb = $this->connection->createQueryBuilder();
$filter = (object) ['command' => 'is:pending', 'string' => '', 'not' => false, 'strict' => false];
$method = new \ReflectionMethod(CampaignRepository::class, 'addSearchCommandWhereClause');
$method->setAccessible(true);
[$expr, $params] = $method->invoke($this->repository, $qb, $filter);
self::assertSame(
'(c.isPublished = :par1) AND (c.publishUp IS NOT NULL) AND (c.publishUp <> \'\') AND (c.publishUp > CURRENT_TIMESTAMP())',
(string) $expr
);
self::assertSame(['par1' => true], $params);
}
public function testGetSearchCommandsContainsExpirationFilters(): void
{
$commands = $this->repository->getSearchCommands();
self::assertContains('mautic.campaign.campaign.searchcommand.isexpired', $commands);
self::assertContains('mautic.campaign.campaign.searchcommand.ispending', $commands);
}
}

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Mautic\CampaignBundle\Entity\Campaign;
use Mautic\CampaignBundle\Entity\Event;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
final class CampaignTest extends TestCase
{
public function testGetEventsByType(): void
{
$campaign = $this->addSomeEvents(new Campaign());
Assert::assertCount(2, $campaign->getEventsByType(Event::TYPE_DECISION));
Assert::assertCount(1, $campaign->getEventsByType(Event::TYPE_ACTION));
Assert::assertCount(1, $campaign->getEventsByType(Event::TYPE_CONDITION));
}
private function addSomeEvents(Campaign $campaign): Campaign
{
$decisionA = new EventFake(1);
$decisionA->setName('Decision A');
$decisionA->setEventType(Event::TYPE_DECISION);
$action = new EventFake(2);
$action->setName('Action A');
$action->setEventType(Event::TYPE_ACTION);
$condition = new EventFake(3);
$condition->setName('Condition A');
$condition->setEventType(Event::TYPE_CONDITION);
$decisionB = new EventFake(4);
$decisionB->setName('Decision B');
$decisionB->setEventType(Event::TYPE_DECISION);
$campaign->addEvent($decisionA->getId(), $decisionA);
$campaign->addEvent($action->getId(), $action);
$campaign->addEvent($condition->getId(), $condition);
$campaign->addEvent($decisionB->getId(), $decisionB);
return $campaign;
}
}

View File

@@ -0,0 +1,173 @@
<?php
namespace Mautic\CampaignBundle\Tests\Entity;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\DBAL\Query\QueryBuilder as DbalQueryBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query\Expr;
use Doctrine\ORM\QueryBuilder as OrmQueryBuilder;
use Mautic\CampaignBundle\Entity\ContactLimiterTrait;
use Mautic\CampaignBundle\Executioner\ContactFinder\Limiter\ContactLimiter;
use Mautic\CoreBundle\Test\Doctrine\MockedConnectionTrait;
class ContactLimiterTraitTest extends \PHPUnit\Framework\TestCase
{
use ContactLimiterTrait;
use MockedConnectionTrait;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|Connection
*/
private \PHPUnit\Framework\MockObject\MockObject $connection;
/**
* @var \PHPUnit\Framework\MockObject\MockObject|EntityManagerInterface
*/
private \PHPUnit\Framework\MockObject\MockObject $entityManager;
protected function setUp(): void
{
$this->connection = $this->getMockedConnection();
$expr = new ExpressionBuilder($this->connection);
$this->connection->method('getExpressionBuilder')
->willReturn($expr);
$this->entityManager = $this->createMock(EntityManagerInterface::class);
$this->entityManager->method('getExpressionBuilder')
->willReturn(new Expr());
}
public function testSpecificContactId(): void
{
$contactLimiter = new ContactLimiter(50, 1);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE l.lead_id = :contactId LIMIT 50', $qb->getSQL());
$this->assertEquals(['contactId' => 1], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) = :contact', $qb->getDQL());
$this->assertEquals(1, $qb->getParameter('contact')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testListOfContacts(): void
{
$contactLimiter = new ContactLimiter(50, null, null, null, [1, 2, 3]);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE l.lead_id IN (:contactIds) LIMIT 50', $qb->getSQL());
$this->assertEquals(['contactIds' => [1, 2, 3]], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) IN(:contactIds)', $qb->getDQL());
$this->assertEquals([1, 2, 3], $qb->getParameter('contactIds')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testMinContactId(): void
{
$contactLimiter = new ContactLimiter(50, null, 4, null);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE l.lead_id >= :minContactId LIMIT 50', $qb->getSQL());
$this->assertEquals(['minContactId' => 4], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) >= :minContactId', $qb->getDQL());
$this->assertEquals(4, $qb->getParameter('minContactId')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testBatchMinContactId(): void
{
$contactLimiter = new ContactLimiter(50, null, 4, null);
$qb = new DbalQueryBuilder($this->connection);
$contactLimiter->setBatchMinContactId(10);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE l.lead_id >= :minContactId LIMIT 50', $qb->getSQL());
$this->assertEquals(['minContactId' => 10], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) >= :minContactId', $qb->getDQL());
$this->assertEquals(10, $qb->getParameter('minContactId')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testMaxContactId(): void
{
$contactLimiter = new ContactLimiter(50, null, null, 10);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE l.lead_id <= :maxContactId LIMIT 50', $qb->getSQL());
$this->assertEquals(['maxContactId' => 10], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) <= :maxContactId', $qb->getDQL());
$this->assertEquals(10, $qb->getParameter('maxContactId')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testMinAndMaxContactId(): void
{
$contactLimiter = new ContactLimiter(50, null, 1, 10);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE l.lead_id BETWEEN :minContactId AND :maxContactId LIMIT 50', $qb->getSQL());
$this->assertEquals(['minContactId' => 1, 'maxContactId' => 10], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) BETWEEN :minContactId AND :maxContactId', $qb->getDQL());
$this->assertEquals(1, $qb->getParameter('minContactId')->getValue());
$this->assertEquals(10, $qb->getParameter('maxContactId')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testThreads(): void
{
$contactLimiter = new ContactLimiter(50, null, null, null, [], 1, 5);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE MOD((l.lead_id + :threadShift), :maxThreads) = 0 LIMIT 50', $qb->getSQL());
$this->assertEquals(['threadShift' => 0, 'maxThreads' => 5], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter);
$this->assertEquals('SELECT WHERE MOD((IDENTITY(l.lead) + :threadShift), :maxThreads) = 0', $qb->getDQL());
$this->assertEquals(0, $qb->getParameter('threadShift')->getValue());
$this->assertEquals(5, $qb->getParameter('maxThreads')->getValue());
$this->assertEquals(50, $qb->getMaxResults());
}
public function testMaxResultsIgnoredForCountQueries(): void
{
$contactLimiter = new ContactLimiter(50, 1);
$qb = new DbalQueryBuilder($this->connection);
$this->updateQueryFromContactLimiter('l', $qb, $contactLimiter, true);
$this->assertEquals('SELECT WHERE l.lead_id = :contactId', $qb->getSQL());
$this->assertEquals(['contactId' => 1], $qb->getParameters());
$qb = new OrmQueryBuilder($this->entityManager);
$this->updateOrmQueryFromContactLimiter('l', $qb, $contactLimiter, true);
$this->assertEquals('SELECT WHERE IDENTITY(l.lead) = :contact', $qb->getDQL());
$this->assertEquals(1, $qb->getParameter('contact')->getValue());
$this->assertEquals(null, $qb->getMaxResults());
}
}

View File

@@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Mautic\CampaignBundle\Entity\Event;
/**
* Allows to use the live Event entity and set the ID.
*/
final class EventFake extends Event
{
private ?int $id;
public function __construct(?int $id = null)
{
$this->id = $id;
}
public function getId(): ?int
{
return $this->id;
}
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Mautic\CampaignBundle\Entity\Campaign;
use Mautic\CampaignBundle\Entity\Event;
use Mautic\CampaignBundle\Entity\EventRepository;
use Mautic\CampaignBundle\Entity\Lead as CampaignMember;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\Assert;
class EventRepositoryFunctionalTest extends MauticMysqlTestCase
{
/**
* @return iterable<string, array{?\DateTime, ?\DateTime, int}>
*/
public static function dataGetContactPendingEventsConsidersCampaignPublishUpAndDown(): iterable
{
yield 'Publish Up and Down not set' => [null, null, 1];
yield 'Publish Up and Down set' => [new \DateTime('-1 day'), new \DateTime('+1 day'), 1];
yield 'Publish Up and Down set with Publish Up in the future' => [new \DateTime('+1 day'), new \DateTime('+2 day'), 0];
yield 'Publish Up and Down set with Publish Down in the past' => [new \DateTime('-2 day'), new \DateTime('-1 day'), 0];
yield 'Publish Up in the past' => [new \DateTime('-1 day'), null, 1];
yield 'Publish Up in the future' => [new \DateTime('+1 day'), null, 0];
yield 'Publish Down in the past' => [null, new \DateTime('-1 day'), 0];
yield 'Publish Down in the future' => [null, new \DateTime('+1 day'), 1];
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataGetContactPendingEventsConsidersCampaignPublishUpAndDown')]
public function testGetContactPendingEventsConsidersCampaignPublishUpAndDown(?\DateTime $publishUp, ?\DateTime $publishDown, int $expectedCount): void
{
$repository = static::getContainer()->get('mautic.campaign.repository.event');
\assert($repository instanceof EventRepository);
$campaign = $this->createCampaign();
$event = $this->createEvent($campaign);
$lead = $this->createLead();
$this->createCampaignMember($lead, $campaign);
$campaign->setPublishUp($publishUp);
$campaign->setPublishDown($publishDown);
$this->em->persist($campaign);
$this->em->flush();
Assert::assertCount($expectedCount, $repository->getContactPendingEvents($lead->getId(), $event->getType()));
}
private function createLead(): Lead
{
$lead = new Lead();
$lead->setFirstname('Test');
$this->em->persist($lead);
return $lead;
}
private function createCampaign(): Campaign
{
$campaign = new Campaign();
$campaign->setName('Test');
$this->em->persist($campaign);
return $campaign;
}
private function createEvent(Campaign $campaign): Event
{
$event = new Event();
$event->setName('test');
$event->setCampaign($campaign);
$event->setType('test.type');
$event->setEventType('action');
$this->em->persist($event);
return $event;
}
private function createCampaignMember(Lead $lead, Campaign $campaign): void
{
$member = new CampaignMember();
$member->setLead($lead);
$member->setCampaign($campaign);
$member->setDateAdded(new \DateTime());
$this->em->persist($member);
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Query\Expr;
use Mautic\CampaignBundle\Entity\Event;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use PHPUnit\Framework\TestCase;
final class EventRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
public function testDecreaseFailedCount(): void
{
$emMock = $this->createMock(EntityManager::class);
$connMock = $this->createMock(Connection::class);
$queryBuilderMock = $this->createMock(QueryBuilder::class);
$expressionMock = $this->createMock(Expr::class);
$queryBuilderMock->expects($this->any())
->method('expr')
->willReturn($expressionMock);
$expressionMock->expects($this->once())
->method('eq')
->with('id', ':id')
->willReturn('id = :id');
$queryBuilderMock->expects($this->any())
->method('expr')
->willReturn($expressionMock);
$expressionMock->expects($this->once())
->method('gt')
->with('failed_count', 0)
->willReturn('failed_count > 0');
$queryBuilderMock->expects($this->once())
->method('update')
->with(MAUTIC_TABLE_PREFIX.'campaign_events')
->willReturn($queryBuilderMock);
$queryBuilderMock->expects($this->once())
->method('set')
->with('failed_count', 'failed_count - 1')
->willReturn($queryBuilderMock);
$queryBuilderMock->expects($this->once())
->method('where')
->with('id = :id')
->willReturn($queryBuilderMock);
$queryBuilderMock->expects($this->once())
->method('andWhere')
->with('failed_count > 0')
->willReturn($queryBuilderMock);
$queryBuilderMock->expects($this->once())
->method('setParameter')
->with('id', $this->equalTo(42))
->willReturn($queryBuilderMock);
$connMock->expects($this->once())
->method('createQueryBuilder')
->willReturn($queryBuilderMock);
$emMock->expects($this->once())
->method('getConnection')
->willReturn($connMock);
$eventRepository = $this->configureRepository(Event::class, $emMock);
$this->connection->method('createQueryBuilder')
->willReturnCallback(fn () => $queryBuilderMock);
$eventMock = $this->createMock(Event::class);
$eventMock->method('getId')
->willReturn(42);
$eventRepository->decreaseFailedCount($eventMock);
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Mautic\CampaignBundle\Entity\Event;
use PHPUnit\Framework\TestCase;
final class EventTest extends TestCase
{
private const TEST_NAME = 'Test Name';
private const DATE = '2021-10-08 08:00:00';
public function testSetTriggerHourWhenEmpty(): void
{
$event = new Event();
$event->setName(self::TEST_NAME);
$event->setTriggerHour('');
$this->assertNull($event->getTriggerHour());
}
public function testSetTriggerHourWhenArray(): void
{
$event = new Event();
$event->setName(self::TEST_NAME);
$event->setTriggerHour(['date' => self::DATE]);
$this->assertEquals(new \DateTime(self::DATE), $event->getTriggerHour());
}
public function testSetTriggerHourWhenString(): void
{
$event = new Event();
$event->setName(self::TEST_NAME);
$event->setTriggerHour(self::DATE);
$this->assertEquals(new \DateTime(self::DATE), $event->getTriggerHour());
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Mautic\CampaignBundle\Tests\Entity;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Persisters\Entity\EntityPersister;
use Doctrine\ORM\UnitOfWork;
use Mautic\CampaignBundle\Entity\FailedLeadEventLog;
use Mautic\CampaignBundle\Entity\LeadEventLog;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use PHPUnit\Framework\TestCase;
final class LeadEventLogRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
#[\PHPUnit\Framework\Attributes\DataProvider('isLastFailedDataProvider')]
public function testIsLastFailed(?LeadEventLog $leadEventLog, bool $expectedResult): void
{
$emMock = $this->createMock(EntityManager::class);
$unitOfWorkMock = $this->createMock(UnitOfWork::class);
$emMock->method('getUnitOfWork')
->willReturn($unitOfWorkMock);
$entityPersisterMock = $this->createMock(EntityPersister::class);
$unitOfWorkMock->method('getEntityPersister')
->willReturn($entityPersisterMock);
$entityPersisterMock->method('load')
->with(['lead' => 42, 'event' => 4242], null, null, [], null, 1, ['dateTriggered' => 'DESC'])
->willReturn($leadEventLog);
$leadEventLogRepository = $this->configureRepository(LeadEventLog::class, $emMock);
$this->connection->method('createQueryBuilder')->willReturnCallback(fn () => new QueryBuilder($this->connection));
$isLastFailed = $leadEventLogRepository->isLastFailed(42, 4242);
$this->assertSame($expectedResult, $isLastFailed);
}
/**
* @return array<string,array<mixed>>
*/
public static function isLastFailedDataProvider(): array
{
$leadEventLogNoFail = new LeadEventLog();
$failedLeadEvent = new FailedLeadEventLog();
$leadEventLogFail = new LeadEventLog();
$leadEventLogFail->setFailedLog($failedLeadEvent);
return [
'no_last_log' => [null, false],
'last_log_no_fail' => [$leadEventLogNoFail, false],
'last_log_fail' => [$leadEventLogFail, true],
];
}
}