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,66 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\CompanyLeadRepository;
use Mautic\LeadBundle\Exception\PrimaryCompanyNotFoundException;
class CompanyLeadRepositoryTest extends \PHPUnit\Framework\TestCase
{
/** @var \PHPUnit\Framework\MockObject\MockObject|CompanyLeadRepository */
private $repoMock;
public function setUp(): void
{
parent::setUp();
$this->repoMock = $this->getMockBuilder(CompanyLeadRepository::class)
->onlyMethods(['getCompaniesByLeadId'])
->disableOriginalConstructor()
->getMock();
}
public function testGetPrimaryCompanyByLeadIdThrowsExceptionIfPrimaryIsMissing(): void
{
$this->repoMock->expects($this->once())
->method('getCompaniesByLeadId')
->willReturn([
[
'company_name' => 'ACME #1',
'is_primary' => false,
],
]);
$this->expectException(PrimaryCompanyNotFoundException::class);
$this->repoMock->getPrimaryCompanyByLeadId(1);
}
public function testGetPrimaryCompanyByLeadIdReturnsCorrectRecord(): void
{
$this->repoMock->expects($this->once())
->method('getCompaniesByLeadId')
->willReturn([
[
'company_name' => 'ACME #1',
'is_primary' => false,
],
[
'company_name' => 'ACME #2',
'is_primary' => true,
],
[
'company_name' => 'ACME #3',
'is_primary' => false,
],
]);
$primary = $this->repoMock->getPrimaryCompanyByLeadId(1);
$this->assertEquals(
[
'company_name' => 'ACME #2',
'is_primary' => true,
],
$primary
);
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Doctrine\Persistence\Mapping\MappingException;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Company;
use Symfony\Component\DomCrawler\Form;
use Symfony\Component\HttpFoundation\Request;
class CompanyTest extends MauticMysqlTestCase
{
public function testChangingPropertiesHydratesFieldChanges(): void
{
$email = 'foo@bar.com';
$company = new Company();
$company->addUpdatedField('email', $email);
$changes = $company->getChanges();
$this->assertFalse(empty($changes['fields']['email']));
$this->assertEquals($email, $changes['fields']['email'][1]);
}
/**
* @throws MappingException
*/
public function testScoreValidationOnCompanyCreate(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/s/companies/new');
$buttonCrawler = $crawler->selectButton('Save & Close');
$form = $buttonCrawler->form();
$form['company[score]']->setValue((string) -3);
$this->testCompanyData($form);
$form['company[score]']->setValue((string) 2147483648);
$this->testCompanyData($form);
}
/**
* @throws MappingException
* @throws ORMException
* @throws OptimisticLockException
*/
public function testMinScoreValidationOnCompanyEdit(): void
{
$company = new Company();
$company->setScore(-1);
$this->em->persist($company);
$this->em->flush();
$companyId = $company->getId();
$crawler = $this->client->request(Request::METHOD_GET, '/s/companies/edit/'.$companyId);
$buttonCrawler = $crawler->selectButton('Save & Close');
$form = $buttonCrawler->form();
$form['company[score]']->setValue((string) -3);
$this->testCompanyData($form);
$form['company[score]']->setValue((string) 2147483648);
$this->testCompanyData($form);
}
/**
* @throws MappingException
*/
private function testCompanyData(Form $form): void
{
$errorMessage = 'This value should be between 0 and 2147483647.';
$this->client->submit($form);
$this->assertTrue($this->client->getResponse()->isOk());
$this->em->clear();
$response = $this->client->getResponse()->getContent();
$this->assertStringContainsString($errorMessage, (string) $response);
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\Company;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
final class CompanyUnitTest extends TestCase
{
public function testChanges(): void
{
$company = new Company();
Assert::assertSame([], $company->getChanges());
$company->setEmail('john.doe@email.com');
$company->setScore(2);
$company->setName('Acquia');
$company->setAddress1('Acquia avenue');
$company->setAddress2('1234');
$company->setPhone('123456789');
$company->setCity('Boston');
$company->setState('MA');
$company->setZipcode('MA1234');
$company->setCountry('US');
$company->setWebsite('acquia.com');
$company->setIndustry('DXP');
$company->setDescription('Supports open source');
Assert::assertSame(
[
'companyemail' => [null, 'john.doe@email.com'],
'score' => [0, 2],
'companyname' => [null, 'Acquia'],
'companyaddress1' => [null, 'Acquia avenue'],
'companyaddress2' => [null, '1234'],
'companyphone' => [null, '123456789'],
'companycity' => [null, 'Boston'],
'companystate' => [null, 'MA'],
'companyzipcode' => [null, 'MA1234'],
'companycountry' => [null, 'US'],
'companywebsite' => [null, 'acquia.com'],
'companyindustry' => [null, 'DXP'],
'companydescription' => [null, 'Supports open source'],
],
$company->getChanges()
);
$company->setEmail('john.doe@email.com - updated');
$company->setScore(5);
$company->setName('Acquia - updated');
$company->setAddress1('Acquia avenue - updated');
$company->setAddress2('1234 - updated');
$company->setPhone('123456789 - updated');
$company->setCity('Boston - updated');
$company->setState('MA - updated');
$company->setZipcode('MA1234 - updated');
$company->setCountry('US - updated');
$company->setWebsite('acquia.com - updated');
$company->setIndustry('DXP - updated');
$company->setDescription('Supports open source - updated');
Assert::assertSame(
[
'companyemail' => ['john.doe@email.com', 'john.doe@email.com - updated'],
'score' => [2, 5],
'companyname' => ['Acquia', 'Acquia - updated'],
'companyaddress1' => ['Acquia avenue', 'Acquia avenue - updated'],
'companyaddress2' => ['1234', '1234 - updated'],
'companyphone' => ['123456789', '123456789 - updated'],
'companycity' => ['Boston', 'Boston - updated'],
'companystate' => ['MA', 'MA - updated'],
'companyzipcode' => ['MA1234', 'MA1234 - updated'],
'companycountry' => ['US', 'US - updated'],
'companywebsite' => ['acquia.com', 'acquia.com - updated'],
'companyindustry' => ['DXP', 'DXP - updated'],
'companydescription' => ['Supports open source', 'Supports open source - updated'],
],
$company->getChanges()
);
}
}

View File

@@ -0,0 +1,70 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\ContactExportScheduler;
use PHPUnit\Framework\Assert;
class ContactExportSchedulerTest extends MauticMysqlTestCase
{
private string $previousTimeZone;
protected function setUp(): void
{
$this->previousTimeZone = date_default_timezone_get();
parent::setUp();
}
protected function beforeTearDown(): void
{
date_default_timezone_set($this->previousTimeZone);
}
public function testScheduledDateTimeIsPersistedAndHydratedProperly(): void
{
$timezone = 'Asia/Taipei';
date_default_timezone_set($timezone);
$exportScheduler = new ContactExportScheduler();
$exportScheduler->setScheduledDateTime(new \DateTimeImmutable());
$this->em->persist($exportScheduler);
$this->em->flush();
$id = $exportScheduler->getId();
$localDate = $exportScheduler->getScheduledDateTime()->format(DateTimeHelper::FORMAT_DB);
$utcDate = $this->convertDateTimezone($localDate, $timezone, 'UTC');
Assert::assertSame($timezone, $exportScheduler->getScheduledDateTime()->getTimezone()->getName(), sprintf('Timezone should be %s.', $timezone));
Assert::assertSame($utcDate, $this->fetchScheduledDate($id), 'Database value should be converted to UTC.');
$this->em->clear();
$timezone = 'America/Cayman';
date_default_timezone_set($timezone);
$exportScheduler = $this->em->find(ContactExportScheduler::class, $id);
$localDate = $this->convertDateTimezone($this->fetchScheduledDate($id), 'UTC', $timezone);
Assert::assertSame($timezone, $exportScheduler->getScheduledDateTime()->getTimezone()->getName(), sprintf('Timezone should be %s.', $timezone));
Assert::assertSame($localDate, $exportScheduler->getScheduledDateTime()->format(DateTimeHelper::FORMAT_DB), sprintf('PHP value should be converted to %s.', $timezone));
}
private function convertDateTimezone(string $date, string $timezoneFrom, string $timezoneTo): string
{
return (new \DateTimeImmutable($date, new \DateTimeZone($timezoneFrom)))
->setTimezone(new \DateTimeZone($timezoneTo))
->format(DateTimeHelper::FORMAT_DB);
}
private function fetchScheduledDate(int $id): string
{
$tablePrefix = self::getContainer()->getParameter('mautic.db_table_prefix');
$connection = $this->em->getConnection();
$query = sprintf('SELECT scheduled_datetime FROM %scontact_export_scheduler WHERE id = :id', $tablePrefix);
return $connection->executeQuery($query, ['id' => $id])
->fetchOne();
}
}

View File

@@ -0,0 +1,202 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\LeadRepository;
use Mautic\LeadBundle\Tests\StandardImportTestHelper;
class CustomFieldRepositoryTraitTest extends StandardImportTestHelper
{
private $fields = [
'firstname' => [
'id' => 2,
'label' => 'First Name',
'alias' => 'firstname',
'type' => 'text',
'group' => 'core',
'object' => 'lead',
'is_fixed' => 1,
],
'lastname' => [
'id' => 3,
'label' => 'Last Name',
'alias' => 'lastname',
'type' => 'text',
'group' => 'core',
'object' => 'lead',
'is_fixed' => 1,
],
'twitter' => [
'id' => 27,
'label' => 'Twitter',
'alias' => 'twitter',
'type' => 'text',
'group' => 'social',
'object' => 'lead',
'is_fixed' => 0,
],
];
private $fieldValues = [
'preferred_profile_image' => 'gravatar',
'firstname' => 'John',
'lastname' => 'Doe',
'twitter' => 'johndoe',
];
protected $fixedFields = [
'firstname' => 'firstname',
'lastname' => 'lastname',
];
protected $baseColumns = [
'preferred_profile_image',
'firstname',
'lastname',
];
protected $fieldGroups = [
'core',
'social',
'personal',
'professional',
];
public function testFormatFieldValues(): void
{
$mockWithTrait = $this->getMockBuilder(LeadRepository::class)
->disableOriginalConstructor()
->enableOriginalClone()
->onlyMethods(['getCustomFieldList', 'getBaseColumns', 'getClassName', 'getFieldGroups'])
->getMock();
$mockWithTrait->method('getCustomFieldList')
->willReturn([$this->fields, $this->fixedFields]);
$mockWithTrait->method('getBaseColumns')
->willReturn($this->baseColumns);
$mockWithTrait->method('getClassName')
->willReturn(\Mautic\LeadBundle\Entity\Lead::class);
$mockWithTrait->method('getFieldGroups')
->willReturn($this->fieldGroups);
$reflectedMockTrait = new \ReflectionObject($mockWithTrait);
$method = $reflectedMockTrait->getMethod('formatFieldValues');
$method->setAccessible(true);
$expected = [
'core' => [
'firstname' => [
'id' => 2,
'label' => 'First Name',
'alias' => 'firstname',
'type' => 'text',
'group' => 'core',
'object' => 'lead',
'is_fixed' => 1,
'value' => 'John',
],
'lastname' => [
'id' => 3,
'label' => 'Last Name',
'alias' => 'lastname',
'type' => 'text',
'group' => 'core',
'object' => 'lead',
'is_fixed' => 1,
'value' => 'Doe',
],
],
'social' => [
'twitter' => [
'id' => 27,
'label' => 'Twitter',
'alias' => 'twitter',
'type' => 'text',
'group' => 'social',
'object' => 'lead',
'is_fixed' => 0,
'value' => 'johndoe',
],
],
'personal' => [],
'professional' => [],
];
$result = $method->invokeArgs($mockWithTrait, [$this->fieldValues]);
$this->assertSame($expected, $result);
}
public function testFormatFieldValuesWhenAFieldIsUnpublished(): void
{
$mockWithTrait = $this->getMockBuilder(LeadRepository::class)
->disableOriginalConstructor()
->enableOriginalClone()
->onlyMethods(['getCustomFieldList', 'getBaseColumns', 'getClassName', 'getFieldGroups'])
->getMock();
$mockWithTrait->method('getCustomFieldList')
->willReturn([$this->fields, $this->fixedFields]);
$mockWithTrait->method('getBaseColumns')
->willReturn($this->baseColumns);
$mockWithTrait->method('getClassName')
->willReturn(\Mautic\LeadBundle\Entity\Lead::class);
$mockWithTrait->method('getFieldGroups')
->willReturn($this->fieldGroups);
$reflectedMockTrait = new \ReflectionObject($mockWithTrait);
$method = $reflectedMockTrait->getMethod('formatFieldValues');
$method->setAccessible(true);
$expected = [
'core' => [
'firstname' => [
'id' => 2,
'label' => 'First Name',
'alias' => 'firstname',
'type' => 'text',
'group' => 'core',
'object' => 'lead',
'is_fixed' => 1,
'value' => 'John',
],
'lastname' => [
'id' => 3,
'label' => 'Last Name',
'alias' => 'lastname',
'type' => 'text',
'group' => 'core',
'object' => 'lead',
'is_fixed' => 1,
'value' => 1,
],
],
'social' => [
'twitter' => [
'id' => 27,
'label' => 'Twitter',
'alias' => 'twitter',
'type' => 'text',
'group' => 'social',
'object' => 'lead',
'is_fixed' => 0,
'value' => 'johndoe',
],
],
'personal' => [],
'professional' => [],
];
$values = $this->fieldValues;
// Simulate unpublished field:
unset($values['lastname']);
$result = $method->invokeArgs($mockWithTrait, [$values]);
$this->assertEquals($expected, $result);
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\DoNotContact;
use Mautic\LeadBundle\Entity\DoNotContactRepository;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\Assert;
final class DoNotContactRepositoryFunctionalTest extends MauticMysqlTestCase
{
public function testGetChannelList(): void
{
$john = $this->createContact('Company A');
$jane = $this->createContact('Company B');
$josh = $this->createContact('Company B');
$this->createDnc('email', $josh, DoNotContact::IS_CONTACTABLE);
$this->createDnc('email', $john, DoNotContact::UNSUBSCRIBED);
$this->createDnc('sms', $john, DoNotContact::BOUNCED);
$this->createDnc('sms', $jane, DoNotContact::MANUAL);
$this->em->flush();
$repository = $this->em->getRepository(DoNotContact::class);
\assert($repository instanceof DoNotContactRepository);
$allDncRecords = $repository->getChannelList(null);
$allSmsRecords = $repository->getChannelList('sms');
Assert::assertCount(3, $allDncRecords, 'Get all records for all channels (dangerous, do not use, there is no limit. One would expect this to return all 4 records, but they are grouped by contact ID.');
Assert::assertCount(2, $allSmsRecords, 'Get all records for sms channel (dangerous, do not use, there is no limit.');
Assert::assertCount(0, $repository->getChannelList('sms', []), 'Get all records for sms channel where the user filtered for a contact that do not exist. It must return an empty array. Not all DNC records.');
Assert::assertCount(1, $repository->getChannelList('sms', [$john->getId()]));
Assert::assertCount(2, $repository->getChannelList('sms', [$john->getId(), $jane->getId(), $josh->getId()]));
Assert::assertSame(['email' => (string) DoNotContact::IS_CONTACTABLE], $allDncRecords[$josh->getId()]);
Assert::assertSame(['email' => (string) DoNotContact::UNSUBSCRIBED, 'sms' => (string) DoNotContact::BOUNCED], $allDncRecords[$john->getId()]);
Assert::assertSame(['sms' => (string) DoNotContact::MANUAL], $allDncRecords[$jane->getId()]);
Assert::assertSame((string) DoNotContact::BOUNCED, $allSmsRecords[$john->getId()]);
Assert::assertSame((string) DoNotContact::MANUAL, $allSmsRecords[$jane->getId()]);
}
public function createDnc(string $channel, Lead $contact, int $reason): DoNotContact
{
$dnc = new DoNotContact();
$dnc->setChannel($channel);
$dnc->setLead($contact);
$dnc->setReason($reason);
$dnc->setDateAdded(new \DateTime());
$this->em->persist($dnc);
return $dnc;
}
private function createContact(string $firstName): Lead
{
$lead = new Lead();
$lead->setFirstname($firstName);
$this->em->persist($lead);
return $lead;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\DoNotContact;
class DoNotContactTest extends \PHPUnit\Framework\TestCase
{
public function testDoNotContactComments(): void
{
$doNotContact = new DoNotContact();
$doNotContact->setComments(null);
$this->assertSame('', $doNotContact->getComments());
$comment = '<script>alert(\'x\')</script>';
$doNotContact->setComments($comment);
$this->assertNotSame($comment, $doNotContact->getComments());
$this->assertSame('alert(\'x\')', $doNotContact->getComments());
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Doctrine\ORM\OptimisticLockException;
use Doctrine\ORM\ORMException;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\EmailBundle\Entity\Stat;
use Mautic\LeadBundle\Entity\FrequencyRule;
use Mautic\LeadBundle\Entity\FrequencyRuleRepository;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\Assert;
class FrequencyRuleRepositoryTest extends MauticMysqlTestCase
{
/**
* @var FrequencyRuleRepository
*/
private $frequencyRuleRepository;
protected function setUp(): void
{
parent::setUp();
$this->frequencyRuleRepository = static::getContainer()->get('mautic.lead.repository.frequency_rule');
}
/**
* @throws OptimisticLockException
* @throws ORMException
*/
public function testCustomFrequencyRuleViolationsMethodReturnsCorrectData(): void
{
$lead = new Lead();
$lead->setFirstname('Firstname');
$lead->setLastname('Lastname');
$lead->setEmail('test@test.com');
$lead->setPhone('555-666-777');
$this->em->persist($lead);
$frequencyRule = new FrequencyRule();
$frequencyRule->setFrequencyNumber(1);
$frequencyRule->setFrequencyTime('DAY');
$frequencyRule->setChannel('email');
$frequencyRule->setDateAdded(new \DateTime());
$frequencyRule->setLead($lead);
$this->em->persist($frequencyRule);
$emailStats1 = new Stat();
$emailStats1->setLead($lead);
$emailStats1->setEmailAddress('testemail@test.test');
$emailStats1->setDateSent(new \DateTime());
$emailStats1->setIsRead(true);
$emailStats1->setIsFailed(false);
$emailStats1->setViewedInBrowser(false);
$emailStats2 = new Stat();
$emailStats2->setLead($lead);
$emailStats2->setEmailAddress('testemail@test.test');
$emailStats2->setDateSent(new \DateTime());
$emailStats2->setIsRead(true);
$emailStats2->setIsFailed(false);
$emailStats2->setViewedInBrowser(false);
$this->em->persist($emailStats1);
$this->em->persist($emailStats2);
$this->em->flush();
$violations = $this->frequencyRuleRepository->getAppliedFrequencyRules('email', [$lead->getId()], 1, 'DAY');
$expectedViolations = [
[
'lead_id' => (string) $lead->getId(),
'frequency_number' => '1',
'frequency_time' => 'DAY',
],
];
Assert::assertSame($expectedViolations, $violations);
}
public function testValidateDefaultParameters(): void
{
$method = new \ReflectionMethod(FrequencyRuleRepository::class, 'validateDefaultParameters');
$method->setAccessible(true);
$this->assertFalse($method->invoke($this->frequencyRuleRepository, false, false));
$this->assertFalse($method->invoke($this->frequencyRuleRepository, false, true));
$this->assertFalse($method->invoke($this->frequencyRuleRepository, true, false));
$this->assertTrue($method->invoke($this->frequencyRuleRepository, true, true));
}
}

View File

@@ -0,0 +1,211 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\Import;
use Mautic\LeadBundle\Tests\StandardImportTestHelper;
class ImportTest extends StandardImportTestHelper
{
public function testSetPath(): void
{
$import = $this->initImportEntity();
$this->assertSame(self::$csvPath, $import->getFilePath());
}
public function testCanProceed(): void
{
$import = $this->initImportEntity();
$this->assertTrue($import->canProceed());
}
public function testIsBackgroundProcess(): void
{
$import = $this->initImportEntity();
$this->assertTrue($import->isBackgroundProcess());
$import->setStatus(Import::MANUAL);
$this->assertFalse($import->isBackgroundProcess());
}
public function testIncreaseInsertedCount(): void
{
$count = 4;
$import = $this->initImportEntity();
$import->setInsertedCount($count);
$this->assertSame($count, $import->getInsertedCount());
$import->increaseInsertedCount();
$this->assertSame(5, $import->getInsertedCount());
}
public function testIncreaseUpdatedCount(): void
{
$count = 4;
$import = $this->initImportEntity();
$import->setUpdatedCount($count);
$this->assertSame($count, $import->getUpdatedCount());
$import->increaseUpdatedCount();
$this->assertSame(5, $import->getUpdatedCount());
}
public function testIncreaseIgnoredCount(): void
{
$count = 4;
$import = $this->initImportEntity();
$import->setIgnoredCount($count);
$this->assertSame($count, $import->getIgnoredCount());
$import->increaseIgnoredCount();
$this->assertSame(5, $import->getIgnoredCount());
}
public function testGetProcessedRows(): void
{
$count = 4;
$import = $this->initImportEntity();
$this->assertSame(0, $import->getProcessedRows());
$import->setIgnoredCount($count);
$import->setUpdatedCount($count);
$import->setInsertedCount($count);
$expectedCount = (3 * $count);
$this->assertSame($expectedCount, $import->getProcessedRows());
$import->increaseIgnoredCount();
$import->increaseIgnoredCount();
$expectedCount = (int) (2 + $expectedCount);
$this->assertSame($expectedCount, $import->getProcessedRows()); // @phpstan-ignore argument.unresolvableType (I don't see anything wrong)
}
public function testGetProgressPercentage(): void
{
$import = $this->initImportEntity()
->setLineCount(100);
$this->assertSame(0.0, $import->getProgressPercentage());
$import->setIgnoredCount(3);
$this->assertEquals(3, $import->getProgressPercentage());
$import->increaseIgnoredCount()
->increaseIgnoredCount();
$this->assertEquals(5, $import->getProgressPercentage());
}
public function testStart(): void
{
$import = $this->initImportEntity();
$this->assertSame(Import::QUEUED, $import->getStatus());
$this->assertNull($import->getDateStarted());
// Date started will be set when the start is called for the first time.
$import->start();
$startDate = $import->getDateStarted();
$this->assertSame(Import::IN_PROGRESS, $import->getStatus());
// But the date started will not change when started for the second time.
$import->end(false);
$import->start();
$this->assertSame($startDate, $import->getDateStarted());
}
public function testEnd(): void
{
$import = $this->initImportEntity();
$this->assertSame(Import::QUEUED, $import->getStatus());
$this->assertNull($import->getDateEnded());
$import->start()->end(false);
$this->assertSame(Import::IMPORTED, $import->getStatus());
$this->assertNotNull($import->getDateEnded());
}
public function testGetRunTime(): void
{
$import = $this->initImportEntity()->start();
$this->assertNull($import->getRunTime());
$import->end(false);
$this->fakeImportStartDate($import, 10 * 60);
$this->assertTrue($import->getRunTime() instanceof \DateInterval);
$this->assertSame(10, $import->getRunTime()->i);
}
public function testGetRunTimeSeconds(): void
{
$import = $this->initImportEntity()->start();
$this->assertSame(0, $import->getRunTimeSeconds());
$import->end(false);
$this->fakeImportStartDate($import, 600);
$this->assertSame(600, $import->getRunTimeSeconds());
}
public function testGetSpeed(): void
{
$import = $this->initImportEntity()->start();
$this->assertSame(0.0, $import->getSpeed());
$import->setInsertedCount(900);
$import->end(false);
$this->fakeImportStartDate($import, 600);
$this->assertSame(1.5, $import->getSpeed());
}
public function testGetSpeedWhenRunTimeIsUnderOneSecond(): void
{
$import = $this->initImportEntity()->start();
$this->assertSame(0.0, $import->getSpeed());
$import->setInsertedCount(3);
$import->end(false);
$this->assertSame(3.0, $import->getSpeed());
}
/**
* Fake the start date to the past to emulate that the import runs for a while.
*
* @param int $runtime in seconds
*/
protected function fakeImportStartDate(Import $import, $runtime = 600)
{
$dateEnded = $import->getDateEnded();
$dateStarted = new \DateTime($dateEnded->format('Y-m-d H:i:s.u'), $dateEnded->getTimezone());
$dateStarted->modify('-'.$runtime.' seconds');
$import->setDateStarted($dateStarted);
}
}

View File

@@ -0,0 +1,209 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\CoreBundle\Tests\Functional\CreateTestEntitiesTrait;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Model\LeadModel;
use Symfony\Component\DomCrawler\Crawler;
use Symfony\Component\HttpFoundation\Request;
#[\PHPUnit\Framework\Attributes\PreserveGlobalState(false)]
#[\PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses]
class LeadCategoryRepositoryFunctionalTest extends MauticMysqlTestCase
{
use CreateTestEntitiesTrait;
/**
* @var array<string, bool>
*/
private $categoryFlags = [
'one' => true,
'two' => false,
'three' => true,
];
private LeadModel $model;
private Lead $lead;
/**
* @var mixed[]
*/
private array $categories;
protected function setUp(): void
{
parent::setUp();
$this->model = self::getContainer()->get('mautic.lead.model.lead');
$this->lead = $this->createLead('John', 'Doe', 'john@doe.com');
// Add three categories to the lead.
// Subscribed 2
// Unsubscribed 1
$this->categories = $this->createCategories();
$this->setLeadCategories($this->lead, $this->categories);
}
protected function beforeBeginTransaction(): void
{
$this->resetAutoincrement(['categories', 'lead_categories']);
}
public function testCategoriesOnContactPreferences(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/s/contacts/contactFrequency/'.$this->lead->getId());
$response = $this->client->getResponse();
$this->assertTrue($response->isOk());
$subscribedCats = $crawler->filter('select[id="lead_contact_frequency_rules_global_categories"]')->filter('option[selected="selected"]');
$this->assertCount(2, $subscribedCats, $crawler->html());
}
public function testCategoriesOnContactPreferencesWhenNewCategoryIsAdded(): void
{
// Add new category, there is no association with the lead.
$this->createCategory('Category extra', 'cat-extra');
// Request the preference on Lead detail page.
$crawler = $this->client->request(Request::METHOD_GET, '/s/contacts/contactFrequency/'.$this->lead->getId());
$response = $this->client->getResponse();
$this->assertTrue($response->isOk());
$subscribedCats = $crawler->filter('select[id="lead_contact_frequency_rules_global_categories"]')->filter('option[selected="selected"]');
// The count should be 3 as we have two subscribed and a new one.
$this->assertCount(3, $subscribedCats);
// The association count is 2.
$this->assertCount(2, $this->model->getLeadCategories($this->lead));
// Now submit the preference form.
$form = $crawler->filterXPath('//form[@name="lead_contact_frequency_rules"]')->form();
$this->client->submit($form);
// The association count is bumped up to three.
$this->assertCount(3, $this->model->getLeadCategories($this->lead));
}
public function testCategoriesOnContactPreferencesWhenNewCategoryIsAddedAndNotSubscribed(): void
{
$categoryExtra = $this->createCategory('Category extra', 'cat-extra');
$crawler = $this->getContactFrequencyCrawler($this->lead);
$subscribedCats = $crawler->filter('select[id="lead_contact_frequency_rules_global_categories"]')->filter('option[selected="selected"]');
// The count should be 3 as we have two subscribed and a new one.
$this->assertCount(3, $subscribedCats);
// The association count is 2.
$this->assertCount(2, $this->model->getLeadCategories($this->lead));
// Now submit the preference form.
$form = $crawler->filterXPath('//form[@name="lead_contact_frequency_rules"]')->form();
$form->setValues(
[
'lead_contact_frequency_rules[global_categories]' => [
$this->categories['one']->getId(),
$this->categories['three']->getId(),
],
]
);
$this->client->submit($form);
$subscribed = $this->model->getLeadCategories($this->lead);
$unSubscribed = $this->model->getUnsubscribedLeadCategoriesIds($this->lead);
$this->assertCount(2, $subscribed);
$this->assertCount(2, $unSubscribed);
$this->assertArrayHasKey($this->categories['one']->getId(), $subscribed);
$this->assertArrayHasKey($this->categories['three']->getId(), $subscribed);
$this->assertArrayHasKey($this->categories['two']->getId(), $unSubscribed);
$this->assertArrayHasKey($categoryExtra->getId(), $unSubscribed);
}
public function testCategoriesOnContactPreferencesWhenNewCategoryIsAddedAndExistingUnsbscribed(): void
{
// Add new category, there is no association with the lead.
$categoryExtra = $this->createCategory('Category extra', 'cat-extra');
$crawler = $this->getContactFrequencyCrawler($this->lead);
$subscribedCats = $crawler->filter('select[id="lead_contact_frequency_rules_global_categories"]')->filter('option[selected="selected"]');
// The count should be 3 as we have two subscribed and a new one.
$this->assertCount(3, $subscribedCats);
// The association count is 2.
$this->assertCount(2, $this->model->getLeadCategories($this->lead));
// Now submit the preference form.
$form = $crawler->filterXPath('//form[@name="lead_contact_frequency_rules"]')->form();
$form->setValues(
[
'lead_contact_frequency_rules[global_categories]' => [
$categoryExtra->getId(),
$this->categories['three']->getId(),
],
]
);
$this->client->submit($form);
$subscribed = $this->model->getLeadCategories($this->lead);
$unSubscribed = $this->model->getUnsubscribedLeadCategoriesIds($this->lead);
$this->assertArrayHasKey($this->categories['three']->getId(), $subscribed);
$this->assertArrayHasKey($categoryExtra->getId(), $subscribed);
$this->assertArrayHasKey($this->categories['one']->getId(), $unSubscribed);
$this->assertArrayHasKey($this->categories['two']->getId(), $unSubscribed);
}
/**
* @return mixed[]
*/
private function createCategories(): array
{
$categories = [];
foreach ($this->categoryFlags as $suffix => $name) {
$categories[$suffix] = $this->createCategory('Category '.$suffix, 'category '.$suffix);
}
$this->em->flush();
return $categories;
}
/**
* @param mixed[] $categories
*/
private function setLeadCategories(Lead $lead, array $categories): void
{
foreach ($this->categoryFlags as $key => $flag) {
$this->createLeadCategory($lead, $categories[$key], $flag);
}
$this->em->flush();
}
private function getContactFrequencyCrawler(Lead $lead): Crawler
{
// Request the preference on Lead detail page.
$crawler = $this->client->request(Request::METHOD_GET, '/s/contacts/contactFrequency/'.$lead->getId());
$response = $this->client->getResponse();
$this->assertTrue($response->isOk());
return $crawler;
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadDevice;
use Mautic\LeadBundle\Entity\LeadDeviceRepository;
class LeadDeviceRepositoryTest extends MauticMysqlTestCase
{
public function testFindExistingDevice(): void
{
$lead = new Lead();
$lead->setEmail('test@example.com');
$this->em->persist($lead);
$initialDevice = new LeadDevice();
$initialDevice->setDateAdded(new \DateTime());
$initialDevice->setLead($lead);
$initialDevice->setClientInfo('client info');
$initialDevice->setDevice('device');
$initialDevice->setDeviceBrand('device brand');
$initialDevice->setDeviceModel('device model');
$initialDevice->setDeviceOs([
'name' => 'GNU/Linux',
'short_name' => 'LIN',
]);
$this->em->persist($initialDevice);
$this->em->flush();
$newDevice = new LeadDevice();
$newDevice->setDateAdded(new \DateTime());
$newDevice->setLead($lead);
$newDevice->setClientInfo('client info');
$newDevice->setDevice('device');
$newDevice->setDeviceBrand('device brand');
$newDevice->setDeviceModel('device model');
$newDevice->setDeviceOs([
'name' => 'Windows',
'short_name' => 'WIN',
]);
/** @var LeadDeviceRepository $leadDeviceRepository */
$leadDeviceRepository = $this->em->getRepository(LeadDevice::class);
$existingDevice = $leadDeviceRepository->findExistingDevice($newDevice);
// Using assertTrue instead of assertNull to reduce test output
$this->assertTrue(
null === $existingDevice,
'The existing device should be null because the device has a different OS.'
);
}
}

View File

@@ -0,0 +1,198 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Model\FieldModel;
use Mautic\LeadBundle\Model\LeadModel;
class LeadFieldRepositoryFunctionalTest extends MauticMysqlTestCase
{
protected $useCleanupRollback = false;
public function testCompareValueEqualsOperator(): void
{
$lead = new Lead();
$lead->setFirstname('John');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'firstname', 'John', 'eq'));
$this->assertFalse($repository->compareValue($lead->getId(), 'firstname', 'Jack', 'eq'));
}
public function testCompareValueNotEqualsOperator(): void
{
$lead = new Lead();
$lead->setFirstname('Ada');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'firstname', 'Annie', 'neq'));
$this->assertFalse($repository->compareValue($lead->getId(), 'firstname', 'Ada', 'neq'));
}
public function testCompareValueEmptyOperator(): void
{
$lead = new Lead();
$lead->setFirstname('Ada');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'lastname', null, 'empty'));
$this->assertFalse($repository->compareValue($lead->getId(), 'firstname', null, 'empty'));
}
public function testCompareValueNotEmptyOperator(): void
{
$lead = new Lead();
$lead->setFirstname('Ada');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'firstname', null, 'notEmpty'));
$this->assertFalse($repository->compareValue($lead->getId(), 'lastname', null, 'notEmpty'));
}
public function testCompareValueStartsWithOperator(): void
{
$lead = new Lead();
$lead->setEmail('MaryWNevarez@armyspy.com');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'email', 'Mary', 'startsWith'));
$this->assertFalse($repository->compareValue($lead->getId(), 'email', 'Unicorn', 'startsWith'));
}
public function testCompareValueEndWithOperator(): void
{
$lead = new Lead();
$lead->setEmail('MaryWNevarez@armyspy.com');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'email', 'armyspy.com', 'endsWith'));
$this->assertFalse($repository->compareValue($lead->getId(), 'email', 'Unicorn', 'endsWith'));
}
public function testCompareValueContainsOperator(): void
{
$lead = new Lead();
$lead->setEmail('MaryWNevarez@armyspy.com');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'email', 'Nevarez', 'contains'));
$this->assertFalse($repository->compareValue($lead->getId(), 'email', 'Unicorn', 'contains'));
}
public function testCompareValueInOperator(): void
{
$lead = new Lead();
$lead->setCountry('United Kingdom');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'country', ['United Kingdom', 'South Africa'], 'in'));
$this->assertFalse($repository->compareValue($lead->getId(), 'country', ['Poland', 'Canada'], 'in'));
}
public function testCompareValueNotInOperator(): void
{
$lead = new Lead();
$lead->setCountry('United Kingdom');
$this->em->persist($lead);
$this->em->flush();
$repository = static::getContainer()->get('mautic.lead.model.field')->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'country', ['Australia', 'Poland'], 'notIn'));
$this->assertFalse($repository->compareValue($lead->getId(), 'country', ['United Kingdom'], 'notIn'));
}
public function testCompareValueInOperatorWithMultiselectField(): void
{
$field = new LeadField();
$field->setType('multiselect');
$field->setObject('lead');
$field->setAlias('colors');
$field->setName('Colors');
$field->setProperties(
[
'list' => [
[
'label' => 'Red',
'value' => 'red',
], [
'label' => 'Green',
'value' => 'green',
], [
'label' => 'Blue',
'value' => 'blue',
], [
'label' => 'Yellow',
'value' => 'yellow',
],
],
]
);
$fieldModel = self::getContainer()->get(FieldModel::class);
\assert($fieldModel instanceof FieldModel);
$fieldModel->saveEntity($field);
$lead = new Lead();
$lead->addUpdatedField('colors', 'green|blue');
$contactModel = self::getContainer()->get(LeadModel::class);
\assert($contactModel instanceof LeadModel);
$contactModel->saveEntity($lead);
$repository = $fieldModel->getRepository();
$this->assertTrue($repository->compareValue($lead->getId(), 'colors', ['green', 'blue'], 'in'));
$this->assertFalse($repository->compareValue($lead->getId(), 'colors', ['red', 'yellow'], 'in'));
}
public function testExcludeUnpublishedField(): void
{
$field = new LeadField();
$field->setType('text');
$field->setObject('lead');
$field->setAlias('colors');
$field->setName('Colors');
$field->setIsPublished(false);
$fieldModel = self::getContainer()->get(FieldModel::class);
$fieldModel->saveEntity($field);
$repository = $fieldModel->getRepository();
$allLeadFields = $repository->getFieldsForObject('lead');
$colorFieldExist = false;
if (!empty($allLeadFields)) {
foreach ($allLeadFields as $field) {
if ('colors' == $field->getAlias()) {
$colorFieldExist = true;
}
}
}
$this->assertFalse($colorFieldExist);
}
}

View File

@@ -0,0 +1,475 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Doctrine\DBAL\Query\Expression\ExpressionBuilder;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Result;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder as OrmQueryBuilder;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Entity\LeadFieldRepository;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
final class LeadFieldRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
private LeadFieldRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->configureRepository(LeadField::class);
}
/**
* Creates and configures mock objects for date value comparison tests.
*
* @return array{
* builderAlias: MockObject&QueryBuilder,
* builderCompare: MockObject&QueryBuilder,
* statementAliasResult: MockObject&Result,
* statementCompareResult: MockObject&Result,
* exprCompare: MockObject&ExpressionBuilder
* }
*/
private function createDateValueComparisonMocks(): array
{
$builderAlias = $this->createMock(QueryBuilder::class);
$builderCompare = $this->createMock(QueryBuilder::class);
$statementAliasResult = $this->createMock(Result::class);
$statementCompareResult = $this->createMock(Result::class);
$exprCompare = $this->createMock(ExpressionBuilder::class);
$this->entityManager->method('getConnection')->willReturn($this->connection);
$builderAlias->method('expr')->willReturn(new ExpressionBuilder($this->connection));
$builderCompare->method('expr')->willReturn($exprCompare);
$this->connection->expects($this->exactly(2))
->method('createQueryBuilder')
->willReturnOnConsecutiveCalls($builderCompare, $builderAlias);
return [
'builderAlias' => $builderAlias,
'builderCompare' => $builderCompare,
'statementAliasResult' => $statementAliasResult,
'statementCompareResult' => $statementCompareResult,
'exprCompare' => $exprCompare,
];
}
/**
* Sets up expectations for the alias query builder.
*/
private function setupAliasQueryBuilderExpectations(
MockObject $builderAlias,
MockObject $statementAliasResult,
): void {
$builderAlias->expects($this->once())
->method('select')
->with('f.alias, f.is_unique_identifer as is_unique, f.type, f.object')
->willReturnSelf();
$builderAlias->expects($this->once())
->method('from')
->with(MAUTIC_TABLE_PREFIX.'lead_fields', 'f')
->willReturnSelf();
$builderAlias->expects($this->once())
->method('where')
->willReturnSelf();
$builderAlias->expects($this->once())
->method('setParameter')
->with('object', 'company')
->willReturnSelf();
$builderAlias->expects($this->once())
->method('orderBy')
->with('f.field_order', 'ASC')
->willReturnSelf();
$builderAlias->expects($this->once())
->method('executeQuery')
->willReturn($statementAliasResult);
}
/**
* Sets up expectations for the compare query builder for contact fields.
*/
private function setupCompareQueryBuilderForContactField(
MockObject $builderCompare,
MockObject $exprCompare,
MockObject $statementCompareResult,
int $contactId,
string $fieldAlias,
?string $value = null,
): void {
$exprCompare->expects($this->exactly(null !== $value ? 2 : 1))
->method('eq')
->willReturnCallback(function (...$parameters) use ($fieldAlias, $value) {
static $invocationCount = 0;
++$invocationCount;
if (1 === $invocationCount) {
$this->assertSame('l.id', $parameters[0]);
$this->assertSame(':lead', $parameters[1]);
}
if (2 === $invocationCount && null !== $value) {
$this->assertSame("l.{$fieldAlias}", $parameters[0]);
$this->assertSame(':value', $parameters[1]);
}
});
$builderCompare->expects($this->once())
->method('select')
->with('l.id')
->willReturnSelf();
$builderCompare->expects($this->once())
->method('from')
->with(MAUTIC_TABLE_PREFIX.'leads', 'l')
->willReturnSelf();
$builderCompare->expects($this->once())
->method('where')
->willReturnSelf();
$parameterCount = null !== $value ? 2 : 1;
$builderCompare->expects($this->exactly($parameterCount))
->method('setParameter')
->willReturnCallback(function (...$parameters) use ($contactId, $value, $builderCompare) {
static $invocationCount = 0;
++$invocationCount;
if (1 === $invocationCount) {
$this->assertSame('lead', $parameters[0]);
$this->assertSame($contactId, $parameters[1]);
}
if (2 === $invocationCount && null !== $value) {
$this->assertSame('value', $parameters[0]);
$this->assertSame($value, $parameters[1]);
}
return $builderCompare;
});
$builderCompare->expects($this->once())
->method('executeQuery')
->willReturn($statementCompareResult);
}
public function testCompareDateValueForContactField(): void
{
$contactId = 12;
$fieldAlias = 'date_field';
$value = '2019-04-30';
$mocks = $this->createDateValueComparisonMocks();
$this->setupAliasQueryBuilderExpectations(
$mocks['builderAlias'],
$mocks['statementAliasResult']
);
// No company column found. Therefore it's a contact field.
$mocks['statementAliasResult']->expects($this->once())
->method('fetchAllAssociative')
->willReturn([]);
$this->setupCompareQueryBuilderForContactField(
$mocks['builderCompare'],
$mocks['exprCompare'],
$mocks['statementCompareResult'],
$contactId,
$fieldAlias,
$value
);
// No contact ID was found by the value so the result should be false.
$mocks['statementCompareResult']->expects($this->once())
->method('fetchAssociative')
->willReturn([]);
$this->assertFalse($this->repository->compareDateValue($contactId, $fieldAlias, $value));
}
public function testCompareDateValueForCompanyField(): void
{
$contactId = 12;
$fieldAlias = 'date_field';
$value = '2019-04-30';
$mocks = $this->createDateValueComparisonMocks();
$this->setupAliasQueryBuilderExpectations(
$mocks['builderAlias'],
$mocks['statementAliasResult']
);
// A company column found. Therefore it's a company field.
$mocks['statementAliasResult']->expects($this->once())
->method('fetchAllAssociative')
->willReturn([['alias' => $fieldAlias]]);
$matcher = $this->exactly(2);
$mocks['exprCompare']->expects($matcher)
->method('eq')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('l.id', $parameters[0]);
$this->assertSame(':lead', $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('company.date_field', $parameters[0]);
$this->assertSame(':value', $parameters[1]);
}
});
$matcher = $this->exactly(2);
$mocks['builderCompare']->expects($matcher)
->method('leftJoin')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('l', $parameters[0]);
$this->assertSame(MAUTIC_TABLE_PREFIX.'companies_leads', $parameters[1]);
$this->assertSame('companies_lead', $parameters[2]);
$this->assertSame('l.id = companies_lead.lead_id', $parameters[3]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('companies_lead', $parameters[0]);
$this->assertSame(MAUTIC_TABLE_PREFIX.'companies', $parameters[1]);
$this->assertSame('company', $parameters[2]);
$this->assertSame('companies_lead.company_id = company.id', $parameters[3]);
}
});
$mocks['builderCompare']->expects($this->once())
->method('select')
->with('l.id')
->willReturnSelf();
$mocks['builderCompare']->expects($this->once())
->method('from')
->with(MAUTIC_TABLE_PREFIX.'leads', 'l')
->willReturnSelf();
$mocks['builderCompare']->expects($this->once())
->method('where')
->willReturnSelf();
$matcher = $this->exactly(2);
$mocks['builderCompare']->expects($matcher)
->method('setParameter')->willReturnCallback(function (...$parameters) use ($matcher, $contactId, $value, $mocks) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('lead', $parameters[0]);
$this->assertSame($contactId, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('value', $parameters[0]);
$this->assertSame($value, $parameters[1]);
}
return $mocks['builderCompare'];
});
$mocks['builderCompare']->expects($this->once())
->method('executeQuery')
->willReturn($mocks['statementCompareResult']);
// A contact ID was found by the value so the result should be true.
$mocks['statementCompareResult']->expects($this->once())
->method('fetchAssociative')
->willReturn(['id' => 456]);
$this->assertTrue($this->repository->compareDateValue($contactId, $fieldAlias, $value));
}
public function testGetListablePublishedFields(): void
{
$query = $this->createQueryMock();
$this->entityManager->expects($this->once())
->method('createQuery')
->with('SELECT f FROM f INDEX BY f.id WHERE f.isListable = 1 AND f.isPublished = 1 ORDER BY f.object ASC')
->willReturn($query);
$query->method('execute')->willReturn([]);
$this->repository->getListablePublishedFields();
}
public function testGetFieldSchemaData(): void
{
$query = $this->createQueryMock();
$this->entityManager->expects($this->once())
->method('createQuery')
->with('SELECT f.alias, f.label, f.type, f.isUniqueIdentifer, f.charLengthLimit FROM f INDEX BY f.alias WHERE f.object = :object')
->willReturn($query);
$result = [];
$query->method('execute')->willReturn($result);
$this->assertSame($result, $this->repository->getFieldSchemaData('lead'));
}
public function testGetFieldThatIsMissingColumnWhenMutlipleColumsMissing(): void
{
$queryBuilder = $this->createMock(OrmQueryBuilder::class);
$this->entityManager->method('createQueryBuilder')
->willReturn($queryBuilder);
$queryBuilder->expects(self::once())
->method('select')
->willReturnSelf();
$queryBuilder->expects(self::once())
->method('from')
->willReturnSelf();
$expr = $this->createMock(Query\Expr::class);
$queryBuilder->expects(self::once())
->method('expr')
->willReturn($expr);
$comparison = $this->createMock(Query\Expr\Comparison::class);
$expr->expects(self::once())
->method('eq')
->willReturn($comparison);
$queryBuilder->expects(self::once())
->method('where')
->with($comparison)
->willReturnSelf();
$queryBuilder->expects(self::once())
->method('orderBy')
->willReturnSelf();
$queryBuilder->expects(self::once())
->method('setMaxResults')
->with(1)
->willReturnSelf();
$query = $this->createMock(AbstractQuery::class);
$queryBuilder->expects(self::once())
->method('getQuery')
->willReturn($query);
$leadField = $this->createMock(LeadField::class);
$query->expects(self::once())
->method('getOneOrNullResult')
->willReturn($leadField);
self::assertSame(
$leadField,
$this->repository->getFieldThatIsMissingColumn()
);
}
private function createQueryMock(): MockObject
{
// This is terrible, but the Query class is final and AbstractQuery doesn't have some methods used.
$query = $this->getMockBuilder(Query::class)
->disableOriginalConstructor()
->onlyMethods([
'setParameters',
'getSingleResult',
'getSQL',
'_doExecute',
'execute',
'setFirstResult',
'setMaxResults',
])
->getMock();
$ormBuilder = new OrmQueryBuilder($this->entityManager);
$this->entityManager->method('createQueryBuilder')->willReturn($ormBuilder);
$this->entityManager->method('createQuery')->willReturn($query);
$query->method('setParameters')->willReturnSelf();
$query->method('setFirstResult')->willReturnSelf();
$query->method('setMaxResults')->willReturnSelf();
return $query;
}
/**
* @return iterable<array{0: string, 1: array<string, int>|array<empty>, 2: bool}>
*/
public static function dataGetEmptyOperators(): iterable
{
yield ['empty', ['id' => 123], true];
yield ['!empty', ['id' => 123], true];
yield ['empty', [], false];
yield ['!empty', [], false];
}
/**
* @param array<string, int>|array<empty> $returnValue
*/
#[DataProvider('dataGetEmptyOperators')]
public function testCompareEmptyDateValueForContactField(string $operator, array $returnValue, bool $expected): void
{
$contactId = 12;
$fieldAlias = 'date_field';
$mocks = $this->createDateValueComparisonMocks();
$this->setupAliasQueryBuilderExpectations(
$mocks['builderAlias'],
$mocks['statementAlias'] = $mocks['statementAliasResult']
);
// No company column found. Therefore it's a contact field.
$mocks['statementAlias']->expects($this->once())
->method('fetchAllAssociative')
->willReturn([]);
$mocks['exprCompare']->expects($this->once())
->method('eq')
->with('l.id', ':lead');
$operators = [
'empty' => 'isNull',
'!empty' => 'isNotNull',
];
$mocks['exprCompare']->expects($this->once())
->method($operators[$operator])
->with('l.date_field');
$mocks['builderCompare']->expects($this->once())
->method('select')
->with('l.id')
->willReturnSelf();
$mocks['builderCompare']->expects($this->once())
->method('from')
->with(MAUTIC_TABLE_PREFIX.'leads', 'l')
->willReturnSelf();
$mocks['builderCompare']->expects($this->once())
->method('where')
->willReturnSelf();
$mocks['builderCompare']->expects($this->once())
->method('setParameter')
->with('lead', $contactId)
->willReturnSelf();
$mocks['builderCompare']->expects($this->once())
->method('executeQuery')
->willReturn($mocks['statementCompare'] = $mocks['statementCompareResult']);
// No contact ID was found by the value so the result should be false.
$mocks['statementCompare']->expects($this->once())
->method('fetchAssociative')
->willReturn($returnValue);
$this->assertSame($expected, $this->repository->compareEmptyDateValue($contactId, $fieldAlias, $operator));
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\LeadField;
class LeadFieldTest extends \PHPUnit\Framework\TestCase
{
public function testNewEntity(): void
{
$leadField = new LeadField();
$this->assertTrue($leadField->isNew());
$this->assertFalse($leadField->getColumnIsNotCreated());
}
public function testColumnNotCreatedForPublishedEntity(): void
{
$leadField = new LeadField();
$leadField->setIsPublished(true);
$this->assertTrue($leadField->getIsPublished());
$leadField->setColumnIsNotCreated();
$this->assertFalse($leadField->getIsPublished(), 'Entity cannot be published until column is not created');
$this->assertTrue($leadField->getColumnIsNotCreated());
$leadField->setColumnWasCreated();
$this->assertTrue($leadField->getIsPublished());
$this->assertFalse($leadField->getColumnIsNotCreated());
}
public function testColumnNotCreatedForUnpublishedEntity(): void
{
$leadField = new LeadField();
$leadField->setIsPublished(false);
$this->assertFalse($leadField->getIsPublished());
$leadField->setColumnIsNotCreated();
$this->assertFalse($leadField->getIsPublished());
$this->assertTrue($leadField->getColumnIsNotCreated());
$leadField->setColumnWasCreated();
$this->assertFalse($leadField->getIsPublished());
$this->assertFalse($leadField->getColumnIsNotCreated());
}
public function testEmailCannotBeUnpublished(): void
{
$leadField = new LeadField();
$leadField->setIsPublished(true);
$this->assertFalse($leadField->disablePublishChange());
$leadField->setAlias('email');
$this->assertTrue($leadField->disablePublishChange());
}
public function testCannotBeUnpublishedUntilColumnIsCreated(): void
{
$leadField = new LeadField();
$leadField->setIsPublished(false);
$this->assertFalse($leadField->disablePublishChange());
$leadField->setColumnIsNotCreated();
$this->assertTrue($leadField->disablePublishChange());
$leadField->setColumnWasCreated();
$this->assertFalse($leadField->disablePublishChange());
}
public function testClone(): void
{
$leadField = new LeadField();
$leadField->setLabel('Test value for custom field 4');
$leadField->setAlias('test_value_for_custom_field_4');
$clonedField = clone $leadField;
$this->assertEquals($leadField->getLabel(), $clonedField->getLabel());
$this->assertEquals($leadField->getAlias(), $clonedField->getAlias());
$this->assertEquals(0, $clonedField->getOrder());
$this->assertTrue($clonedField->getIsCloned());
}
}

View File

@@ -0,0 +1,72 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\AbstractMauticTestCase;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Entity\ListLead;
class LeadListRepositoryFunctionalTest extends AbstractMauticTestCase
{
public function testCheckLeadSegmentsByIds(): void
{
$lead = $this->createLead();
$segmentA = $this->createSegment();
$segmentB = $this->createSegment('B');
$segmentC = $this->createSegment('C');
$this->createSegmentMember($segmentA, $lead);
$this->createSegmentMember($segmentB, $lead, true);
$leadListRepository = $this->em->getRepository(LeadList::class);
$result = $leadListRepository->checkLeadSegmentsByIds($lead, [$segmentA->getId()]);
$this->assertTrue($result);
$result = $leadListRepository->checkLeadSegmentsByIds($lead, [$segmentB->getId()]);
$this->assertFalse($result);
$result = $leadListRepository->checkLeadSegmentsByIds($lead, [$segmentC->getId()]);
$this->assertFalse($result);
$result = $leadListRepository->checkLeadSegmentsByIds($lead, [$segmentA->getId(), $segmentB->getId(), $segmentC->getId()]);
$this->assertTrue($result);
}
private function createLead(): Lead
{
$lead = new Lead();
$lead->setFirstname('Contact');
$lead->setEmail('test@test.com');
$this->em->persist($lead);
$this->em->flush();
return $lead;
}
private function createSegment(string $suffix = 'A'): LeadList
{
$segment = new LeadList();
$segment->setName("Segment $suffix");
$segment->setPublicName("Segment $suffix");
$segment->setAlias("segment-$suffix");
$this->em->persist($segment);
$this->em->flush();
return $segment;
}
protected function createSegmentMember(LeadList $segment, Lead $lead, bool $isManuallyRemoved = false): void
{
$segmentMember = new ListLead();
$segmentMember->setLead($lead);
$segmentMember->setList($segment);
$segmentMember->setManuallyRemoved($isManuallyRemoved);
$segmentMember->setDateAdded(new \DateTime());
$this->em->persist($segmentMember);
$this->em->flush();
}
}

View File

@@ -0,0 +1,361 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\Query\Expr;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Entity\LeadListRepository;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Psr\Cache\InvalidArgumentException;
class LeadListRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
private LeadListRepository $repository;
/**
* @var QueryBuilder&MockObject
*/
private MockObject $queryBuilderMock;
/**
* @var Expr&MockObject
*/
private MockObject $expressionMock;
protected function setUp(): void
{
parent::setUp();
$this->connection = $this->createMock(Connection::class);
$this->queryBuilderMock = $this->createMock(QueryBuilder::class);
$this->expressionMock = $this->createMock(Expr::class);
$this->repository = $this->configureRepository(LeadList::class);
}
public function testIsContactInAnySegmentFalse(): void
{
$contactId = 1;
$this->mockIsContactInAnySegment($contactId, []);
self::assertFalse($this->repository->isContactInAnySegment($contactId));
}
public function testIsContactInAnySegmentTrue(): void
{
$contactId = 1;
$this->mockIsContactInAnySegment($contactId, [1]);
self::assertTrue($this->repository->isContactInAnySegment($contactId));
}
public function testIsNotContactInAnySegmentTrue(): void
{
$contactId = 1;
$this->mockIsContactInAnySegment($contactId, []);
self::assertTrue($this->repository->isNotContactInAnySegment($contactId));
}
public function testIsNotContactInAnySegmentFalse(): void
{
$contactId = 1;
$this->mockIsContactInAnySegment($contactId, [1]);
self::assertFalse($this->repository->isNotContactInAnySegment($contactId));
}
public function testIsContactInSegmentsNone(): void
{
$contactId = 1;
$expectedSegmentIds = [1];
$queryResult = [];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertFalse($this->repository->isContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsContactInSegmentsOne(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertTrue($this->repository->isContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsContactInSegmentsAll(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1, 2];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertTrue($this->repository->isContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsNotContactInSegmentsNone(): void
{
$contactId = 1;
$expectedSegmentIds = [1];
$queryResult = [0];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertTrue($this->repository->isNotContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsNotContactInSegmentsOne(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertFalse($this->repository->isNotContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsNotContactInSegmentsAll(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1, 2];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertFalse($this->repository->isNotContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsContactInAllSegmentsNoSegments(): void
{
$contactId = 1;
$expectedSegmentIds = [1];
$queryResult = [];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertFalse($this->repository->isContactInAllSegments($contactId, $expectedSegmentIds));
}
public function testIsContactInAllSegmentsDoesNotMatch(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertFalse($this->repository->isContactInAllSegments($contactId, $expectedSegmentIds));
}
public function testIsContactInAllSegmentsMatches(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1, 2];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertTrue($this->repository->isContactInSegments($contactId, $expectedSegmentIds));
}
public function testIsNotContactInAllSegmentsMatches(): void
{
$contactId = 1;
$expectedSegmentIds = [1];
$queryResult = [];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertTrue($this->repository->isNotContactInAllSegments($contactId, $expectedSegmentIds));
}
public function testIsNotContactInAllSegmentsDoesNotMatch(): void
{
$contactId = 1;
$expectedSegmentIds = [1, 2];
$queryResult = [1, 2];
$this->mockIsContactInSegments($contactId, $expectedSegmentIds, $queryResult);
self::assertFalse($this->repository->isNotContactInAllSegments($contactId, $expectedSegmentIds));
}
/**
* @param array<int> $queryResult
*/
private function mockIsContactInAnySegment(int $contactId, array $queryResult): void
{
$prefix = MAUTIC_TABLE_PREFIX;
$sql = <<<SQL
SELECT leadlist_id
FROM {$prefix}lead_lists_leads
WHERE lead_id = ?
AND manually_removed = 0
LIMIT 1
SQL;
$this->connection->expects(self::once())
->method('executeQuery')
->with($sql, [$contactId], [\PDO::PARAM_INT])
->willReturn($this->result);
$this->result->expects(self::once())
->method('fetchFirstColumn')
->willReturn($queryResult);
}
/**
* @param array<int> $expectedSegmentIds
* @param array<int> $queryResult
*/
private function mockIsContactInSegments(int $contactId, array $expectedSegmentIds, array $queryResult): void
{
$prefix = MAUTIC_TABLE_PREFIX;
$sql = <<<SQL
SELECT leadlist_id
FROM {$prefix}lead_lists_leads
WHERE lead_id = ?
AND leadlist_id IN (?)
AND manually_removed = 0
SQL;
$this->connection->expects(self::once())
->method('executeQuery')
->with(
$sql,
[$contactId, $expectedSegmentIds],
[\PDO::PARAM_INT, ArrayParameterType::INTEGER]
)
->willReturn($this->result);
$this->result->expects(self::once())
->method('fetchFirstColumn')
->willReturn($queryResult);
}
/**
* @throws InvalidArgumentException
*/
public function testGetMultipleLeadCounts(): void
{
$listIds = [765, 766];
$counts = [100, 200];
$queryResult = [
[
'leadlist_id' => $listIds[0],
'thecount' => $counts[0],
],
[
'leadlist_id' => $listIds[1],
'thecount' => $counts[1],
],
];
$this->mockGetLeadCount($queryResult, false);
$this->queryBuilderMock->expects(self::once())
->method('from')
->with(MAUTIC_TABLE_PREFIX.'lead_lists_leads', 'l')
->willReturnSelf();
$this->expressionMock->expects(self::once())
->method('in')
->with('l.leadlist_id', $listIds)
->willReturnSelf();
$this->expressionMock->expects(self::once())
->method('eq')
->with('l.manually_removed', ':false')
->willReturnSelf();
$this->queryBuilderMock->expects(self::once())
->method('setParameter')
->with('false', false, 'boolean')
->willReturnSelf();
self::assertSame(array_combine($listIds, $counts), $this->repository->getLeadCount($listIds));
}
public function testGetSingleLeadCount(): void
{
$listIds = [765];
$counts = [100];
$queryResult = [
[
'leadlist_id' => $listIds[0],
'thecount' => $counts[0],
],
];
$this->mockGetLeadCount($queryResult);
$fromPart = [
[
'alias' => 'l',
'table' => MAUTIC_TABLE_PREFIX.'lead_lists_leads',
],
];
$this->queryBuilderMock->expects(self::once())
->method('getQueryPart')
->willReturn($fromPart);
$matcher = self::exactly(2);
$this->queryBuilderMock->expects($matcher)
->method('from')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(MAUTIC_TABLE_PREFIX.'lead_lists_leads', $parameters[0]);
$this->assertSame('l', $parameters[1]);
return $this->queryBuilderMock;
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(MAUTIC_TABLE_PREFIX.'lead_lists_leads', $parameters[0]);
$this->assertSame('l USE INDEX ('.MAUTIC_TABLE_PREFIX.'manually_removed)', $parameters[1]);
return $this->queryBuilderMock;
}
});
$matcher = self::exactly(2);
$this->expressionMock->expects($matcher)
->method('eq')->willReturnCallback(function (...$parameters) use ($matcher, $listIds) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('l.leadlist_id', $parameters[0]);
$this->assertSame($listIds[0], $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('l.manually_removed', $parameters[0]);
$this->assertSame(':false', $parameters[1]);
}
return $this->expressionMock;
});
self::assertSame($counts[0], $this->repository->getLeadCount($listIds));
}
/**
* @param array<mixed> $queryResult
*/
private function mockGetLeadCount(array $queryResult, bool $addParam = true): void
{
$this->connection->method('createQueryBuilder')
->willReturn($this->queryBuilderMock);
$this->queryBuilderMock->expects(self::once())
->method('select')
->with('count(l.lead_id) as thecount, l.leadlist_id')
->willReturnSelf();
$this->queryBuilderMock->expects(self::exactly(2))
->method('expr')
->willReturn($this->expressionMock);
if ($addParam) {
$this->queryBuilderMock->expects(self::once())
->method('setParameter')
->with('false', false, 'boolean')
->willReturnSelf();
}
$this->queryBuilderMock->expects(self::once())
->method('where')
->with($this->expressionMock)
->willReturnSelf();
$this->queryBuilderMock->expects(self::once())
->method('executeQuery')
->willReturn($this->result);
$this->result->expects(self::once())
->method('fetchAllAssociative')
->willReturn($queryResult);
}
}

View File

@@ -0,0 +1,253 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\LeadList;
use PHPUnit\Framework\Assert;
final class LeadListTest extends \PHPUnit\Framework\TestCase
{
public function testAddLegacyParamsWithEmptyFilters(): void
{
$entity = new LeadList();
$this->assertSame([], $entity->getFilters());
}
public function testAddLegacyParamsWithLegacyFilters(): void
{
$entity = new LeadList();
$entity->setFilters(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'display' => 'John Doe',
'filter' => '4',
],
]
);
$this->assertSame(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'display' => 'John Doe',
'filter' => '4',
],
],
$entity->getFilters()
);
}
public function testAddLegacyParamsWithNewFilters(): void
{
$entity = new LeadList();
$entity->setFilters(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'properties' => [
'display' => 'John Doe',
'filter' => '4',
],
],
[
'object' => 'lead',
'glue' => 'and',
'field' => 'city',
'type' => 'text',
'operator' => '=',
'properties' => [
'filter' => 'Prague',
],
],
]
);
$this->assertSame(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'properties' => [
'display' => 'John Doe',
'filter' => '4',
],
'filter' => '4',
'display' => 'John Doe',
],
[
'object' => 'lead',
'glue' => 'and',
'field' => 'city',
'type' => 'text',
'operator' => '=',
'properties' => [
'filter' => 'Prague',
],
'filter' => 'Prague',
'display' => null,
],
],
$entity->getFilters()
);
}
public function testAddLegacyParamsWithHybridFilters(): void
{
$entity = new LeadList();
$entity->setFilters(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'filter' => 'outdated_id',
'display' => 'Outdated Name',
'properties' => [
'display' => 'John Doe',
'filter' => '4',
],
],
]
);
$this->assertSame(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'filter' => '4',
'display' => 'John Doe',
'properties' => [
'display' => 'John Doe',
'filter' => '4',
],
],
],
$entity->getFilters()
);
}
#[\PHPUnit\Framework\Attributes\DataProvider('setIsGlobalDataProvider')]
public function testSetIsGlobal($value, $expected, array $changes): void
{
$segment = new LeadList();
$segment->setIsGlobal($value);
Assert::assertSame($expected, $segment->getIsGlobal());
Assert::assertSame($changes, $segment->getChanges());
}
public static function setIsGlobalDataProvider(): iterable
{
yield [null, false, ['isGlobal' => [true, false]]];
yield [true, true, []];
yield [false, false, ['isGlobal' => [true, false]]];
yield ['', false, ['isGlobal' => [true, false]]];
yield [0, false, ['isGlobal' => [true, false]]];
yield ['string', true, []];
}
#[\PHPUnit\Framework\Attributes\DataProvider('setIsPreferenceCenterDataProvider')]
public function testSetIsPreferenceCenter($value, $expected, array $changes): void
{
$segment = new LeadList();
$segment->setIsPreferenceCenter($value);
Assert::assertSame($expected, $segment->getIsPreferenceCenter());
Assert::assertSame($changes, $segment->getChanges());
}
public static function setIsPreferenceCenterDataProvider(): iterable
{
yield [null, false, []];
yield [true, true, ['isPreferenceCenter' => [false, true]]];
yield [false, false, []];
yield ['', false, []];
yield [0, false, []];
yield ['string', true, ['isPreferenceCenter' => [false, true]]];
}
public function testFirstFilterGlueIsAlwaysAnd(): void
{
$entity = new LeadList();
$entity->setFilters([
[
'object' => 'lead',
'glue' => 'or',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'properties' => [
'display' => 'John Doe',
'filter' => '4',
],
],
[
'object' => 'lead',
'glue' => 'and',
'field' => 'city',
'type' => 'text',
'operator' => '=',
'properties' => [
'filter' => 'Prague',
],
],
]);
$this->assertSame(
[
[
'object' => 'lead',
'glue' => 'and',
'field' => 'owner_id',
'type' => 'lookup_id',
'operator' => '=',
'properties' => [
'display' => 'John Doe',
'filter' => '4',
],
'filter' => '4',
'display' => 'John Doe',
],
[
'object' => 'lead',
'glue' => 'and',
'field' => 'city',
'type' => 'text',
'operator' => '=',
'properties' => [
'filter' => 'Prague',
],
'filter' => 'Prague',
'display' => null,
],
],
$entity->getFilters(), 'The first filter glue must be "and".');
}
}

View File

@@ -0,0 +1,208 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadRepository;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class LeadRepositoryFunctionalTest extends MauticMysqlTestCase
{
private Lead $lead;
protected function setUp(): void
{
parent::setUp();
$this->lead = $this->createLead();
}
public function testPointsAreAdded(): void
{
$model = static::getContainer()->get('mautic.lead.model.lead');
$this->lead->adjustPoints(100);
$model->saveEntity($this->lead);
$this->assertEquals(200, $this->lead->getPoints());
$changes = $this->lead->getChanges(true);
$this->assertEquals(200, $changes['points'][1]);
}
public function testPointsAreSubtracted(): void
{
$model = static::getContainer()->get('mautic.lead.model.lead');
$this->lead->adjustPoints(100, Lead::POINTS_SUBTRACT);
$model->saveEntity($this->lead);
$this->assertEquals(0, $this->lead->getPoints());
$changes = $this->lead->getChanges(true);
$this->assertEquals(0, $changes['points'][1]);
}
public function testPointsAreMultiplied(): void
{
$model = static::getContainer()->get('mautic.lead.model.lead');
$this->lead->adjustPoints(2, Lead::POINTS_MULTIPLY);
$model->saveEntity($this->lead);
$this->assertEquals(200, $this->lead->getPoints());
$changes = $this->lead->getChanges(true);
$this->assertEquals(200, $changes['points'][1]);
}
public function testPointsAreDivided(): void
{
$model = static::getContainer()->get('mautic.lead.model.lead');
$this->lead->adjustPoints(2, Lead::POINTS_DIVIDE);
$model->saveEntity($this->lead);
$this->assertEquals(50, $this->lead->getPoints());
$changes = $this->lead->getChanges(true);
$this->assertEquals(50, $changes['points'][1]);
}
public function testMixedOperatorPointsAreCalculated(): void
{
$model = static::getContainer()->get('mautic.lead.model.lead');
$this->lead->adjustPoints(100, Lead::POINTS_SUBTRACT);
$this->lead->adjustPoints(120, Lead::POINTS_ADD);
$this->lead->adjustPoints(2, Lead::POINTS_MULTIPLY);
$this->lead->adjustPoints(4, Lead::POINTS_DIVIDE);
$model->saveEntity($this->lead);
$this->assertEquals(60, $this->lead->getPoints());
$changes = $this->lead->getChanges(true);
$this->assertEquals(60, $changes['points'][1]);
}
public function testMixedModelAndRepositorySavesDoNotDoublePoints(): void
{
$model = static::getContainer()->get('mautic.lead.model.lead');
$this->lead->adjustPoints(120, Lead::POINTS_ADD);
$model->saveEntity($this->lead);
// Changes should be stored with points
$changes = $this->lead->getChanges(true);
$this->assertEquals(220, $changes['points'][1]);
// Points should now not be in changes
$model->saveEntity($this->lead);
$changes = $this->lead->getChanges(true);
$this->assertFalse(isset($changes['points']));
// Points should remain the same
$model->saveEntity($this->lead);
$this->em->getRepository(Lead::class)->saveEntity($this->lead);
$this->assertEquals(220, $this->lead->getPoints());
}
/**
* @param string[]|string $emails
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataForTestAjaxGetLeadsByFieldValue')]
public function testAjaxGetLeadsByFieldValue($emails, bool $createFlag, int $expectedCount): void
{
$this->createLeads($emails, $createFlag);
$payload = [
'action' => 'lead:getLeadIdsByFieldValue',
'field' => 'email',
'value' => $emails,
];
$this->client->xmlHttpRequest(Request::METHOD_GET, '/s/ajax', $payload);
$this->assertSame(Response::HTTP_OK, $this->client->getResponse()->getStatusCode(), print_r(json_decode($this->client->getResponse()->getContent(), true), true));
$contentArray = json_decode($this->client->getResponse()->getContent(), true);
$this->assertCount($expectedCount, $contentArray['items']);
}
/**
* @return array<string, array<int, int|string|bool|string[]>>
*/
public static function dataForTestAjaxGetLeadsByFieldValue(): iterable
{
yield 'Email passed as string with associated contact' => [
'john@doe.com', // Email
true,
1, // Count
];
yield 'Email passed as string without associated contact' => [
'john@doe.com', // Email
false,
0, // Count
];
yield 'Email passed as array with associated contacts' => [
['john@doe.com', 'doe@doe.com'], // Email
true,
2, // Count
];
yield 'Email passed as array without associated contacts' => [
['john@doe.com', 'doe@doe.com'], // Email
false,
0, // Count
];
}
/**
* @param string[]|string $emails
*/
private function createLeads($emails, bool $flag): void
{
if (!$flag) {
return;
}
if (!is_array($emails)) {
$emails = [$emails];
}
foreach ($emails as $email) {
$this->createLead($email);
}
}
public function testIfLeadExists(): void
{
/** @var LeadRepository $repo */
$repo = $this->em->getRepository(Lead::class);
$this->assertFalse($repo->exists('654'));
$lead = $this->createLead();
$this->assertTrue($repo->exists((string) $lead->getId()));
}
private function createLead(string $email = ''): Lead
{
$lead = new Lead();
$lead->setPoints(100);
if ($email) {
$lead->setEmail($email);
}
$this->em->persist($lead);
$this->em->flush();
return $lead;
}
}

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\ORM\AbstractQuery;
use Mautic\CoreBundle\Test\Doctrine\DBALMocker;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadRepository;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
class LeadRepositoryTest extends \PHPUnit\Framework\TestCase
{
use RepositoryConfiguratorTrait;
private LeadRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->configureRepository(Lead::class);
}
public function testBooleanWithPrepareDbalFieldsForSave(): void
{
$trait = $this->createMock(LeadRepository::class);
$fields = [
'true' => true,
'false' => false,
'string' => 'blah',
];
$reflection = new \ReflectionObject($trait);
$method = $reflection->getMethod('prepareDbalFieldsForSave');
$method->setAccessible(true);
$method->invokeArgs($trait, [&$fields]);
$this->assertEquals(1, $fields['true']);
$this->assertEquals(0, $fields['false']);
$this->assertEquals('blah', $fields['string']);
}
/**
* Ensure that the emails are bound separately as parameters according to
* https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/query-builder.html#line-number-0a267d5a2c69797a7656aae33fcc140d16b0a566-72.
*/
public function testBuildQueryForGetLeadsByFieldValue(): void
{
$dbalMock = new DBALMocker($this);
$mock = $this->getMockBuilder(LeadRepository::class)
->disableOriginalConstructor()
->onlyMethods(['getEntityManager'])
->getMock();
$mock->method('getEntityManager')
->willReturn($dbalMock->getMockEm());
$reflection = new \ReflectionClass(LeadRepository::class);
$refMethod = $reflection->getMethod('buildQueryForGetLeadsByFieldValue');
$refMethod->setAccessible(true);
$refMethod->invoke($mock, 'email', ['test@example.com', 'test2@example.com']);
$parameters = $dbalMock->getQueryPart('parameters');
$this->assertCount(2, $parameters, 'There should be two parameters bound because that\'s the number of emails we passed into the method.');
}
/**
* Ensure that the array_combine return value matches the old style.
*/
public function testGetLeadsByFieldValueArrayMapReturn(): void
{
/** @var MockObject&LeadRepository */
$repository = $this->getMockBuilder(LeadRepository::class)
->disableOriginalConstructor()
->onlyMethods(['getEntities', 'buildQueryForGetLeadsByFieldValue'])
->getMock();
$contact = new Lead();
$contact->setEmail('test@example.com');
$contact2 = new Lead();
$contact2->setEmail('test2@example.com');
$entities = [$contact, $contact2];
$repository->method('getEntities')->willReturn($entities);
$repository->method('buildQueryForGetLeadsByFieldValue')->willReturn(null);
$contacts = $repository->getLeadsByFieldValue('email', ['test@example.com', 'test2@example.com']);
$this->assertSame($entities, $contacts, 'When getting leads without indexing by column, it should match the expected result.');
$contacts = $repository->getLeadsByFieldValue('email', ['test@example.com', 'test2@example.com'], null, true);
$expected = ['test@example.com', 'test2@example.com'];
$this->assertSame($expected, array_keys($contacts), 'When getting leads with indexing by column, it should match the expected result.');
}
/**
* Ensure that we will join each table with the same alias only once.
*/
public function testApplySearchQueryRelationshipJoinOnlyOnce(): void
{
$queryBuilder = new QueryBuilder($this->connection);
$queryBuilder->select('*')->from(MAUTIC_TABLE_PREFIX.'table_a');
$tableB = [
'alias' => 'alias_b',
'from_alias' => MAUTIC_TABLE_PREFIX.'table_a',
'table' => 'table_b',
'condition' => 'condition_b',
];
$tableC = [
'alias' => 'alias_c',
'from_alias' => MAUTIC_TABLE_PREFIX.'table_a',
'table' => 'table_c',
'condition' => 'condition_c',
];
$this->repository->applySearchQueryRelationship(
$queryBuilder,
[$tableB, $tableC, $tableB], // Sending 2 table B here.
true
);
Assert::assertSame(
'SELECT * FROM '.MAUTIC_TABLE_PREFIX.'table_a INNER JOIN '.MAUTIC_TABLE_PREFIX.'table_b alias_b ON condition_b INNER JOIN '.MAUTIC_TABLE_PREFIX.'table_c alias_c ON condition_c GROUP BY l.id',
$queryBuilder->getSQL()
);
}
public function testGetContactIdsByEmails(): void
{
$emails = [
'somebody1@anywhere.com',
'somebody2@anywhere.com',
];
$query = $this->createMock(AbstractQuery::class);
$query->expects(self::once())
->method('setParameter')
->with('emails', $emails, ArrayParameterType::STRING)
->willReturn($query);
$query->expects(self::once())
->method('getArrayResult')
->willReturn([
0 => [
'id' => '1',
],
1 => [
'id' => '2',
],
]);
$this->entityManager->expects(self::once())
->method('createQuery')
->willReturn($query);
self::assertEquals(
[1, 2],
$this->repository->getContactIdsByEmails($emails)
);
}
public function testGetUniqueIdentifiersOperator(): void
{
$this->repository->setUniqueIdentifiersOperator(CompositeExpression::TYPE_AND);
$reflection = new \ReflectionClass(LeadRepository::class);
$refMethod = $reflection->getMethod('getUniqueIdentifiersWherePart');
$refMethod->setAccessible(true);
$this->assertEquals('andWhere', $refMethod->invoke($this->repository));
$this->repository->setUniqueIdentifiersOperator(CompositeExpression::TYPE_OR);
$this->assertEquals('orWhere', $refMethod->invoke($this->repository));
}
public function testUpdateLastActiveWithId(): void
{
$this->entityManager->expects($this->once())
->method('getConnection')
->willReturn($this->connection);
$this->repository->updateLastActive(1);
}
public function testUpdateLastActiveWithNoId(): void
{
$this->entityManager->expects($this->never())
->method('getConnection')
->willReturn($this->connection);
$this->repository->updateLastActive(null); // @phpstan-ignore-line this tests if we provide null instead which actually happens.
}
}

View File

@@ -0,0 +1,348 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Entity\IpAddress;
use Mautic\CoreBundle\Form\RequestTrait;
use Mautic\LeadBundle\Entity\DoNotContact;
use Mautic\LeadBundle\Entity\FrequencyRule;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadEventLog;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class LeadTest extends TestCase
{
use RequestTrait;
public function testPreferredChannels(): void
{
$frequencyRules = [
'channel1' => [
'frequencyNumber' => '',
'frequencyTime' => '',
'preferredChannel' => 0,
'pauseFromDate' => '2016-01-01',
'pauseToDate' => '2100-01-01',
],
'channel2' => [
'frequencyNumber' => '',
'frequencyTime' => '',
'preferredChannel' => 1,
'pauseFromDate' => '',
'pauseToDate' => '',
],
'channel3' => [
'frequencyNumber' => '7',
'frequencyTime' => FrequencyRule::TIME_DAY, // 210
'preferredChannel' => 0,
'pauseFromDate' => '',
'pauseToDate' => '',
],
'channel4' => [
'frequencyNumber' => '',
'frequencyTime' => '',
'preferredChannel' => 0,
'pauseFromDate' => '',
'pauseToDate' => '',
],
'channel5' => [
'frequencyNumber' => '10',
'frequencyTime' => FrequencyRule::TIME_WEEK, // 40
'preferredChannel' => 0,
'pauseFromDate' => '',
'pauseToDate' => '',
],
'channel6' => [
'frequencyNumber' => 10,
'frequencyTime' => FrequencyRule::TIME_MONTH, // 10
'preferredChannel' => 0,
'pauseFromDate' => '',
'pauseToDate' => '',
],
];
$lead = new Lead();
foreach ($frequencyRules as $channel => $rule) {
$frequencyRule = (new FrequencyRule())
->setPreferredChannel($rule['preferredChannel'])
->setFrequencyNumber($rule['frequencyNumber'])
->setFrequencyTime($rule['frequencyTime'])
->setChannel($channel)
->setPauseFromDate(($rule['pauseFromDate']) ? new \DateTime($rule['pauseFromDate']) : null)
->setPauseToDate(($rule['pauseToDate']) ? new \DateTime($rule['pauseToDate']) : null);
$lead->addFrequencyRule($frequencyRule);
}
$dnc = (new DoNotContact())->setChannel('channel4');
$lead->addDoNotContactEntry($dnc);
$channelRules = Lead::generateChannelRules($lead->getFrequencyRules()->toArray(), $lead->getDoNotContact()->toArray());
$this->assertEquals(['channel2', 'channel3', 'channel5', 'channel6', 'channel1', 'channel4'], array_keys($channelRules));
}
public function testAdjustPoints(): void
{
// new lead
$this->adjustPointsTest(5, $this->getLeadChangedArray(0, 5), new Lead());
$this->adjustPointsTest(5, $this->getLeadChangedArray(0, 5), new Lead(), 'plus');
$this->adjustPointsTest(5, $this->getLeadChangedArray(0, -5), new Lead(), 'minus');
$this->adjustPointsTest(5, [], new Lead(), 'times');
$this->adjustPointsTest(5, [], new Lead(), 'divide');
// existing lead
$lead = new Lead();
$lead->setPoints(5);
$this->adjustPointsTest(5, $this->getLeadChangedArray(5, 10), $lead);
$this->adjustPointsTest(5, $this->getLeadChangedArray(10, 15), $lead);
$this->adjustPointsTest(10, $this->getLeadChangedArray(15, 150), $lead, 'times');
$this->adjustPointsTest(10, $this->getLeadChangedArray(150, 15), $lead, 'divide');
}
public function testCustomFieldGetterSetters(): void
{
$lead = new Lead();
$fields = [
'core' => [
'notes' => [
'alias' => 'notes',
'label' => 'Notes',
'type' => 'textarea',
'value' => 'Blah blah blah',
],
'test' => [
'alias' => 'test',
'label' => 'Test',
'type' => 'textarea',
'value' => 'Test blah',
],
],
];
$lead->setFields($fields);
// This should not killover with a segmentation fault due to a loop
$lead->setNotes('hello');
// Not using getNotes because it conflicts with an existing method and not sure what to do about that yet
$lead->setTest('hello');
$this->assertEquals('hello', $lead->getTest());
}
public function testDataIsCleanedCorrectly(): void
{
$fields = [
'core' => [
'boolean' => [
'alias' => 'boolean',
'label' => 'Boolean',
'type' => 'boolean',
'value' => false,
],
'dateField' => [
'alias' => 'dateField',
'label' => 'Date Time',
'type' => 'datetime',
'value' => '12-12-2017 23:00:00',
],
'multiselect' => [
'alias' => 'multi',
'label' => 'Multi Select',
'type' => 'multiselect',
'value' => ['a', 'b', 'c'],
],
],
];
$data = [
'boolean' => 'yes',
'dateField' => '12-12-2017 22:03:59',
'multi' => 'a|b',
];
$this->cleanFields($data, $fields['core']['boolean']);
$this->cleanFields($data, $fields['core']['dateField']);
$this->cleanFields($data, $fields['core']['multiselect']);
$testDateObject = new \DateTime('12-12-2017 22:03:59');
$this->assertEquals($testDateObject->format('Y-m-d H:i:s'), $data['dateField']);
$this->assertEquals(1, $data['boolean']);
$this->assertEquals(['a', 'b'], $data['multi']);
}
public function testCleanBooleanAndNumberAsNullAreNotConverted(): void
{
$fields = [
'core' => [
'boolean' => [
'alias' => 'boolean',
'label' => 'Boolean',
'type' => 'boolean',
'value' => false,
],
'number' => [
'alias' => 'number',
'label' => 'Number',
'type' => 'number',
'value' => '1234',
],
],
];
$data = [
'boolean' => null,
'number' => null,
];
$this->cleanFields($data, $fields['core']['boolean']);
$this->cleanFields($data, $fields['core']['number']);
$this->assertEquals(null, $data['boolean']);
$this->assertEquals(null, $data['number']);
}
public function testAttributionDateIsAdded(): void
{
$lead = new Lead();
$lead->addUpdatedField('attribution', 100);
$lead->checkAttributionDate();
$this->assertEquals((new \DateTime())->format('Y-m-d'), $lead->getFieldValue('attribution_date'));
$this->assertNotEmpty($lead->getChanges());
}
public function testAttributionDateIsRemoved(): void
{
$lead = new Lead();
$lead->setFields(
[
'core' => [
'attribution_date' => [
'type' => 'date',
'value' => '2017-09-09',
],
'attribution' => [
'type' => 'int',
'value' => 100,
],
],
]
);
$lead->addUpdatedField('attribution', 0);
$lead->checkAttributionDate();
$this->assertNull($lead->getFieldValue('attribution_date'));
$this->assertNotEmpty($lead->getChanges());
}
public function testAttributionDateIsNotChangedWhen0ChangedToNull(): void
{
$lead = new Lead();
$lead->setFields(
[
'core' => [
'attribution_date' => [
'type' => 'date',
'value' => 0,
],
'attribution' => [
'type' => 'int',
'value' => 0,
],
],
]
);
$lead->checkAttributionDate();
$this->assertEmpty($lead->getChanges());
}
public function testChangingPropertiesHydratesFieldChanges(): void
{
$email = 'foo@bar.com';
$lead = new Lead();
$lead->addUpdatedField('email', $email);
$changes = $lead->getChanges();
$this->assertFalse(empty($changes['email']));
$this->assertFalse(empty($changes['fields']['email']));
$this->assertEquals($email, $changes['email'][1]);
$this->assertEquals($email, $changes['fields']['email'][1]);
}
public function testIpAddressChanges(): void
{
$ip1 = (new IpAddress())->setIpAddress('1.2.3.4');
$ip2 = (new IpAddress())->setIpAddress('1.2.3.5');
$contact = new Lead();
$this->assertCount(0, $contact->getChanges());
$contact->addIpAddress($ip1);
/** @var mixed[] $changes */
$changes = $contact->getChanges();
$this->assertSame(['1.2.3.4' => $ip1], $changes['ipAddressList']);
$contact->addIpAddress($ip2);
/** @var mixed[] $changes */
$changes = $contact->getChanges();
$this->assertSame(['1.2.3.4' => $ip1, '1.2.3.5' => $ip2], $changes['ipAddressList']);
}
public function testGetLastEventLogByAction(): void
{
$lead = new Lead();
$lead->addEventLog((new LeadEventLog())->setAction('first')->setDateAdded(new \DateTime('2017-01-01')));
$lead->addEventLog((new LeadEventLog())->setAction('first')->setDateAdded(new \DateTime('2018-01-01')));
$lead->addEventLog($lastFirst = (new LeadEventLog())->setAction('first')->setDateAdded(new \DateTime('2019-01-01')));
$lead->addEventLog((new LeadEventLog())->setAction('second')->setDateAdded(new \DateTime('2017-01-01')));
$lead->addEventLog((new LeadEventLog())->setAction('second')->setDateAdded(new \DateTime('2018-01-01')));
$lead->addEventLog($lastSecond = (new LeadEventLog())->setAction('second')->setDateAdded(new \DateTime('2019-01-01')));
Assert::assertSame($lastFirst, $lead->getLastEventLogByAction('first'));
Assert::assertSame($lastSecond, $lead->getLastEventLogByAction('second'));
Assert::assertNull($lead->getLastEventLogByAction('third'));
}
/**
* @param bool $operator
*/
private function adjustPointsTest($points, $expected, Lead $lead, $operator = false): void
{
if ($operator) {
$lead->adjustPoints($points, $operator);
} else {
$lead->adjustPoints($points);
}
$this->assertEquals($expected, $lead->getChanges());
}
/**
* @param int $oldValue
* @param int $newValue
*
* @return array
*/
private function getLeadChangedArray($oldValue = 0, $newValue = 0)
{
return [
'points' => [
0 => $oldValue,
1 => $newValue,
],
];
}
}

View File

@@ -0,0 +1,53 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Entity\ListLead;
use Mautic\LeadBundle\Entity\ListLeadRepository;
final class ListLeadRepositoryTest extends MauticMysqlTestCase
{
private ListLeadRepository $listLeadRepository;
public function setUp(): void
{
parent::setUp();
$this->listLeadRepository = static::getContainer()->get(ListLeadRepository::class);
}
public function testGetContactsCountBySegment(): void
{
$filters = ['manually_removed' => 0];
$contact = new Lead();
$segment = new LeadList();
$segmentMember = new ListLead();
$segment->setName('A segment');
$segment->setPublicName('A segment');
$segment->setAlias('asegment');
$segmentMember->setLead($contact);
$segmentMember->setList($segment);
$segmentMember->setManuallyRemoved(false);
$segmentMember->setDateAdded(new \DateTime());
$this->em->persist($contact);
$this->em->persist($segment);
$this->em->persist($segmentMember);
$this->em->flush();
$this->assertSame(1, $this->listLeadRepository->getContactsCountBySegment($segment->getId(), $filters));
$segmentMember->setManuallyRemoved(true);
$this->em->persist($segmentMember);
$this->em->flush();
$this->assertSame(0, $this->listLeadRepository->getContactsCountBySegment($segment->getId(), $filters));
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use Mautic\LeadBundle\Entity\Tag;
use Mautic\LeadBundle\Entity\TagRepository;
class TagRepositoryTest extends \PHPUnit\Framework\TestCase
{
use RepositoryConfiguratorTrait;
public function testGetTagByNameOrCreateNewOneWithSomeExistingTag(): void
{
$fetchedEntity = new Tag('sometag');
$mockRepository = $this->getMockBuilder(TagRepository::class)
->disableOriginalConstructor()
->onlyMethods(['findOneBy'])
->getMock();
$mockRepository->expects($this->once())
->method('findOneBy')
->with(['tag' => 'sometag'])
->willReturn($fetchedEntity);
$this->assertSame($fetchedEntity, $mockRepository->getTagByNameOrCreateNewOne('sometag'));
}
public function testGetTagByNameOrCreateNewOneWithSomeNewTag(): void
{
$mockRepository = $this->getMockBuilder(TagRepository::class)
->disableOriginalConstructor()
->onlyMethods(['findOneBy'])
->getMock();
$mockRepository->expects($this->once())
->method('findOneBy')
->with(['tag' => 'sometag'])
->willReturn(null);
$newEntity = $mockRepository->getTagByNameOrCreateNewOne('sometag');
$this->assertSame('sometag', $newEntity->getTag());
$this->assertNull($newEntity->getId());
}
public function testGetTagByNameOrCreateNewOneInputFilter(): void
{
$fetchedEntity = new Tag('hello" world');
$mockRepository = $this->getMockBuilder(TagRepository::class)
->disableOriginalConstructor()
->onlyMethods(['findOneBy'])
->getMock();
$mockRepository->expects($this->once())
->method('findOneBy')
->with(['tag' => 'hello" world'])
->willReturn($fetchedEntity);
$this->assertSame($fetchedEntity, $mockRepository->getTagByNameOrCreateNewOne('hello" world'));
}
public function testRemoveMinusFromTags(): void
{
$repository = $this->configureRepository(Tag::class);
$tags = [
'sometag1',
'-sometag2',
'sometag3',
'-sometag4',
];
$expected = [
'sometag1',
'sometag2',
'sometag3',
'sometag4',
];
$this->assertSame($expected, $repository->removeMinusFromTags($tags));
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\Tag;
class TagTest extends \PHPUnit\Framework\TestCase
{
public function testSetTagByConstructor(): void
{
$entity = new Tag('tagA');
$this->assertSame('tagA', $entity->getTag());
}
public function testSetTagBySetter(): void
{
$entity = new Tag();
$entity->setTag('tagA');
$this->assertSame('tagA', $entity->getTag());
}
public function testTagValidation(): void
{
$sampleTags = [
'hello world' => 'hello world',
'hello" world' => 'hello" world',
'trim whitespace' => ' trim whitespace ',
'trim tab' => "\ttrim tab\t",
'console.log(hello)' => '<script>console.log(hello)</script>',
'oěř§ůú.' => 'oěř§ůú.',
];
foreach ($sampleTags as $expected => $tag) {
$entity = new Tag($tag);
$this->assertSame($expected, $entity->getTag());
}
}
public function testDisabledValidation(): void
{
$sampleTags = [
'hello world' => 'hello world',
'hello&#34; world' => 'hello&#34; world',
'oěř§ůú.' => 'oěř§ůú.',
];
foreach ($sampleTags as $expected => $tag) {
$entity = new Tag($tag, false);
$this->assertSame($expected, $entity->getTag());
}
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Entity;
use Mautic\LeadBundle\Entity\UtmTag;
use PHPUnit\Framework\Assert;
class UtmTagTest extends \PHPUnit\Framework\TestCase
{
#[\PHPUnit\Framework\Attributes\DataProvider('setUtmTag')]
public function testSetUtmContent(string $utmContent, int $expected): void
{
$utmTag = new UtmTag();
$utmTag->setUtmContent($utmContent);
Assert::assertEquals($expected, mb_strlen($utmTag->getUtmContent()));
}
/**
* @return iterable<array<int,int|string>>
*/
public static function setUtmTag(): iterable
{
yield ['custom', 6];
yield ['UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 UTM content longer like 191 ', 191];
}
#[\PHPUnit\Framework\Attributes\DataProvider('utmTagsDataProvider')]
public function testHasUtmTags(?string $utmCampaign, ?string $utmSource, ?string $utmMedium, ?string $utmContent, ?string $utmTerm, bool $expectedResult): void
{
$utmTag = new UtmTag();
$utmTag->setUtmCampaign($utmCampaign);
$utmTag->setUtmSource($utmSource);
$utmTag->setUtmMedium($utmMedium);
$utmTag->setUtmContent($utmContent);
$utmTag->setUtmTerm($utmTerm);
$this->assertEquals($expectedResult, $utmTag->hasUtmTags());
}
/**
* @return array<string|array<bool|string|''>>
*/
public static function utmTagsDataProvider(): array
{
return [
'All tags are empty' => ['', '', '', '', '', false],
'Only utmCampaign is set' => ['campaign', '', '', '', '', true],
'Only utmSource is set' => ['', 'source', '', '', '', true],
'Only utmMedium is set' => ['', '', 'medium', '', '', true],
'Only utmContent is set' => ['', '', '', 'content', '', true],
'Only utmTerm is set' => ['', '', '', '', 'term', true],
'All tags are set' => ['campaign', 'source', 'medium', 'content', 'term', true],
];
}
}