Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,247 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\EmailBundle\Helper\EmailValidator;
|
||||
use Mautic\LeadBundle\Deduplicate\CompanyDeduper;
|
||||
use Mautic\LeadBundle\Entity\Company;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Symfony\Component\HttpFoundation\Session\Session;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(\Mautic\CoreBundle\Helper\AbstractFormFieldHelper::class)]
|
||||
class CompanyModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var FieldModel|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $leadFieldModel;
|
||||
|
||||
/**
|
||||
* @var \PHPUnit\Framework\MockObject\MockObject|Session
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $session;
|
||||
|
||||
/**
|
||||
* @var EmailValidator|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $emailValidator;
|
||||
|
||||
/**
|
||||
* @var CompanyDeduper|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $companyDeduper;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->leadFieldModel = $this->createMock(FieldModel::class);
|
||||
$this->session = $this->createMock(Session::class);
|
||||
$this->emailValidator = $this->createMock(EmailValidator::class);
|
||||
$this->companyDeduper = $this->createMock(CompanyDeduper::class);
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\TestDox('Ensure that an array value is flattened before saving')]
|
||||
public function testArrayValueIsFlattenedBeforeSave(): void
|
||||
{
|
||||
/** @var CompanyModel $companyModel */
|
||||
$companyModel = $this->getMockBuilder(CompanyModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods([])
|
||||
->getMock();
|
||||
|
||||
$company = new Company();
|
||||
$company->setFields(
|
||||
[
|
||||
'core' => [
|
||||
'multiselect' => [
|
||||
'type' => 'multiselect',
|
||||
'alias' => 'multiselect',
|
||||
'value' => 'abc|123',
|
||||
],
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$companyModel->setFieldValues($company, ['multiselect' => ['abc', 'def']]);
|
||||
|
||||
$updatedFields = $company->getUpdatedFields();
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
'multiselect' => 'abc|def',
|
||||
],
|
||||
$updatedFields
|
||||
);
|
||||
}
|
||||
|
||||
public function testImportCompanySkipIfExistsTrue(): void
|
||||
{
|
||||
$companyModel = $this->getCompanyModelForImport();
|
||||
|
||||
$duplicatedCompany = $this->createMock(Company::class);
|
||||
$duplicatedCompany->method('getProfileFields')->willReturn(['companyfield'=> 'xxx']);
|
||||
$companyDeduper = $this->getCompanyDeduperForImport($duplicatedCompany);
|
||||
|
||||
$this->setProperty($companyModel, CompanyModel::class, 'companyDeduper', $companyDeduper);
|
||||
$duplicatedCompany->expects($this->exactly(0))->method('addUpdatedField');
|
||||
$companyModel->importCompany([], [], null, false, true);
|
||||
}
|
||||
|
||||
public function testImportCompanySkipIfExistsFalse(): void
|
||||
{
|
||||
$companyModel = $this->getCompanyModelForImport();
|
||||
|
||||
$duplicatedCompany = $this->createMock(Company::class);
|
||||
$duplicatedCompany->method('getProfileFields')->willReturn(['companyfield'=> 'xxx']);
|
||||
$companyDeduper = $this->getCompanyDeduperForImport($duplicatedCompany);
|
||||
|
||||
$this->setProperty($companyModel, CompanyModel::class, 'companyDeduper', $companyDeduper);
|
||||
$duplicatedCompany->expects($this->once())->method('addUpdatedField');
|
||||
$companyModel->importCompany([], [], null, false, false);
|
||||
}
|
||||
|
||||
public function testImportHtmlFieldsForCompany(): void
|
||||
{
|
||||
$companyModel = $this->getMockBuilder(CompanyModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchCompanyFields', 'getFieldData'])
|
||||
->getMock();
|
||||
|
||||
$companyModel->method('fetchCompanyFields')->willReturn(
|
||||
[
|
||||
[
|
||||
'alias' => 'companyfield',
|
||||
'defaultValue' => '',
|
||||
'type' => 'text',
|
||||
],
|
||||
[
|
||||
'alias' => 'custom_html_field',
|
||||
'defaultValue' => '',
|
||||
'type' => 'html',
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
$data = ['companyfield' => 'test', 'custom_html_field' => '<p>html content</p>'];
|
||||
$companyModel->method('getFieldData')
|
||||
->willReturn($data);
|
||||
$this->setSecurity($companyModel);
|
||||
|
||||
$companyModel->method('getFieldData')->willReturn($data);
|
||||
|
||||
$duplicatedCompany = $this->createMock(Company::class);
|
||||
$duplicatedCompany->method('getProfileFields')->willReturn($data);
|
||||
|
||||
$companyDeduper = $this->getCompanyDeduperForImport($duplicatedCompany);
|
||||
$this->setProperty($companyModel, CompanyModel::class, 'companyDeduper', $companyDeduper);
|
||||
|
||||
$duplicatedCompany->expects($this->exactly(2))->method('addUpdatedField');
|
||||
$companyModel->importCompany([], [], null, false, false);
|
||||
}
|
||||
|
||||
private function getCompanyModelForImport()
|
||||
{
|
||||
$companyModel = $this->getMockBuilder(CompanyModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchCompanyFields', 'getFieldData'])
|
||||
->getMock();
|
||||
|
||||
$companyModel->method('fetchCompanyFields')->willReturn(
|
||||
[
|
||||
[
|
||||
'alias' => 'companyfield',
|
||||
'defaultValue' => '',
|
||||
'type' => 'text',
|
||||
],
|
||||
]
|
||||
);
|
||||
$companyModel->method('getFieldData')->willReturn(['companyfield' => 'xxx']);
|
||||
$this->setSecurity($companyModel);
|
||||
|
||||
return $companyModel;
|
||||
}
|
||||
|
||||
private function getCompanyDeduperForImport(Company $duplicatedCompany)
|
||||
{
|
||||
$companyDeduper = $this->createMock(CompanyDeduper::class);
|
||||
|
||||
$companyDeduper->method('checkForDuplicateCompanies')->willReturn([$duplicatedCompany]);
|
||||
|
||||
return $companyDeduper;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set protected property to an object.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $class
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function setProperty($object, $class, $property, $value): void
|
||||
{
|
||||
$reflectedProp = new \ReflectionProperty($class, $property);
|
||||
$reflectedProp->setAccessible(true);
|
||||
$reflectedProp->setValue($object, $value);
|
||||
}
|
||||
|
||||
public function testExtractCompanyDataFromImport(): void
|
||||
{
|
||||
/** @var CompanyModel $companyModel */
|
||||
$companyModel = $this->getMockBuilder(CompanyModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['fetchCompanyFields'])
|
||||
->getMock();
|
||||
|
||||
$companyModel->method('fetchCompanyFields')
|
||||
->willReturn([
|
||||
['alias' => 'companyname'],
|
||||
['alias' => 'companyemail'],
|
||||
['alias' => 'companyindustry'],
|
||||
]);
|
||||
|
||||
$fields = [
|
||||
'email' => 'i_contact_email',
|
||||
'companyemail' => 'i_company_email',
|
||||
'company' => 'i_company_name',
|
||||
'companyindustry' => 'i_company_industry',
|
||||
];
|
||||
$data= [
|
||||
'i_contact_email' => 'PennyKMoore@dayrep.com',
|
||||
'i_company_email' => 'turbochicken@dayrep.com',
|
||||
'i_company_name' => 'Turbo chicken',
|
||||
'i_company_industry' => 'Biotechnology',
|
||||
];
|
||||
|
||||
[$companyFields, $companyData] = $companyModel->extractCompanyDataFromImport($fields, $data);
|
||||
|
||||
$expectedCompanyFields = [
|
||||
'companyemail' => 'i_company_email',
|
||||
'companyindustry' => 'i_company_industry',
|
||||
'companyname' => 'i_company_name',
|
||||
];
|
||||
$expectedCompanyData = [
|
||||
'i_company_email' => 'turbochicken@dayrep.com',
|
||||
'i_company_industry' => 'Biotechnology',
|
||||
'i_company_name' => 'Turbo chicken',
|
||||
];
|
||||
|
||||
$this->assertSame($expectedCompanyFields, $companyFields);
|
||||
$this->assertSame($expectedCompanyData, $companyData);
|
||||
}
|
||||
|
||||
private function setSecurity(CompanyModel $companyModel): void
|
||||
{
|
||||
$security = $this->createMock(CorePermissions::class);
|
||||
$security->method('hasEntityAccess')
|
||||
->willReturn(true);
|
||||
$security->method('isGranted')
|
||||
->willReturn(true);
|
||||
|
||||
$reflection = new \ReflectionClass($companyModel);
|
||||
$property = $reflection->getProperty('security');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($companyModel, $security);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\FormBundle\Entity\Field;
|
||||
use Mautic\LeadBundle\Model\CompanyReportData;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\ReportBundle\Event\ReportGeneratorEvent;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\CoversClass(CompanyReportData::class)]
|
||||
class CompanyReportDataTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var TranslatorInterface
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $translator;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->translator = $this->createMock(Translator::class);
|
||||
|
||||
$this->translator->method('trans')
|
||||
->willReturnCallback(
|
||||
fn ($key) => $key
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetCompanyData(): void
|
||||
{
|
||||
$fieldModelMock = $this->createMock(FieldModel::class);
|
||||
|
||||
$field1 = new Field();
|
||||
$field1->setType('boolean');
|
||||
$field1->setAlias('boolField');
|
||||
$field1->setLabel('boolFieldLabel');
|
||||
|
||||
$field2 = new Field();
|
||||
$field2->setType('email');
|
||||
$field2->setAlias('emailField');
|
||||
$field2->setLabel('emailFieldLabel');
|
||||
|
||||
$fields = [
|
||||
$field1,
|
||||
$field2,
|
||||
];
|
||||
|
||||
$fieldModelMock->expects($this->once())
|
||||
->method('getEntities')
|
||||
->willReturn($fields);
|
||||
|
||||
$companyReportData = new CompanyReportData($fieldModelMock, $this->translator);
|
||||
|
||||
$result = $companyReportData->getCompanyData();
|
||||
|
||||
$expected = [
|
||||
'comp.id' => [
|
||||
'alias' => 'comp_id',
|
||||
'label' => 'mautic.lead.report.company.company_id',
|
||||
'type' => 'int',
|
||||
'link' => 'mautic_company_action',
|
||||
],
|
||||
'companies_lead.is_primary' => [
|
||||
'label' => 'mautic.lead.report.company.is_primary',
|
||||
'type' => 'bool',
|
||||
],
|
||||
'companies_lead.date_added' => [
|
||||
'label' => 'mautic.lead.report.company.date_added',
|
||||
'type' => 'datetime',
|
||||
],
|
||||
'comp.boolField' => [
|
||||
'label' => 'mautic.report.field.company.label',
|
||||
'type' => 'bool',
|
||||
],
|
||||
'comp.emailField' => [
|
||||
'label' => 'mautic.report.field.company.label',
|
||||
'type' => 'email',
|
||||
],
|
||||
];
|
||||
|
||||
$this->assertSame($expected, $result);
|
||||
}
|
||||
|
||||
public function testEventHasCompanyColumns(): void
|
||||
{
|
||||
$fieldModelMock = $this->createMock(FieldModel::class);
|
||||
|
||||
$eventMock = $this->createMock(ReportGeneratorEvent::class);
|
||||
|
||||
$field = new Field();
|
||||
$field->setType('email');
|
||||
$field->setAlias('email');
|
||||
$field->setLabel('Email');
|
||||
|
||||
$fieldModelMock->expects($this->once())
|
||||
->method('getEntities')
|
||||
->willReturn([$field]);
|
||||
|
||||
$eventMock->expects($this->once())
|
||||
->method('hasColumn')
|
||||
->with('comp.id')
|
||||
->willReturn(true);
|
||||
|
||||
$companyReportData = new CompanyReportData($fieldModelMock, $this->translator);
|
||||
|
||||
$result = $companyReportData->eventHasCompanyColumns($eventMock);
|
||||
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testEventDoesNotHaveCompanyColumns(): void
|
||||
{
|
||||
$fieldModelMock = $this->createMock(FieldModel::class);
|
||||
|
||||
$eventMock = $this->createMock(ReportGeneratorEvent::class);
|
||||
|
||||
$field = new Field();
|
||||
$field->setType('email');
|
||||
$field->setAlias('email');
|
||||
$field->setLabel('Email');
|
||||
|
||||
$fieldModelMock->expects($this->once())
|
||||
->method('getEntities')
|
||||
->willReturn([$field]);
|
||||
|
||||
$eventMock->expects($this->any())
|
||||
->method('hasColumn')
|
||||
->willReturn(false);
|
||||
|
||||
$companyReportData = new CompanyReportData($fieldModelMock, $this->translator);
|
||||
|
||||
$result = $companyReportData->eventHasCompanyColumns($eventMock);
|
||||
|
||||
$this->assertFalse($result);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\LeadBundle\Entity\LeadField;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
|
||||
final class FieldModelCustomFieldsFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
protected $useCleanupRollback = false;
|
||||
|
||||
public function testGetLeadFields(): void
|
||||
{
|
||||
/** @var FieldModel $fieldModel */
|
||||
$fieldModel = $this->getContainer()->get('mautic.lead.model.field');
|
||||
$fields = $fieldModel->getLeadFields();
|
||||
$expected = count(FieldModel::$coreFields);
|
||||
$this->assertGreaterThanOrEqual($expected, count($fields));
|
||||
}
|
||||
|
||||
public function testLeadFieldCustomFields(): void
|
||||
{
|
||||
/** @var FieldModel $fieldModel */
|
||||
$fieldModel = $this->getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
$fields = $fieldModel->getLeadFieldCustomFields();
|
||||
$this->assertEmpty($fields, 'There are no Custom Fields.');
|
||||
|
||||
// Add field.
|
||||
$leadField = new LeadField();
|
||||
$leadField->setName('Test Field')
|
||||
->setAlias('test_field')
|
||||
->setType('string')
|
||||
->setObject('lead');
|
||||
$fieldModel->saveEntity($leadField);
|
||||
|
||||
$fields = $fieldModel->getLeadFieldCustomFields();
|
||||
$this->assertEquals(1, count($fields));
|
||||
}
|
||||
|
||||
public function testGetLeadCustomFieldsSchemaDetails(): void
|
||||
{
|
||||
/** @var FieldModel $fieldModel */
|
||||
$fieldModel = $this->getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
// Add field.
|
||||
$leadField = new LeadField();
|
||||
$leadField->setName('Test Field')
|
||||
->setAlias('test_field')
|
||||
->setType('string')
|
||||
->setObject('lead');
|
||||
$fieldModel->saveEntity($leadField);
|
||||
|
||||
$schemas = $fieldModel->getLeadFieldCustomFieldSchemaDetails();
|
||||
$this->assertEquals(1, count($schemas));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Doctrine\DBAL\Logging\SQLLogger;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CoreBundle\Doctrine\Helper\ColumnSchemaHelper;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\LeadField;
|
||||
use Mautic\LeadBundle\Entity\LeadFieldRepository;
|
||||
use Mautic\LeadBundle\Field\CustomFieldColumn;
|
||||
use Mautic\LeadBundle\Field\Dispatcher\FieldSaveDispatcher;
|
||||
use Mautic\LeadBundle\Field\FieldList;
|
||||
use Mautic\LeadBundle\Field\LeadFieldDeleter;
|
||||
use Mautic\LeadBundle\Field\LeadFieldSaver;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\LeadBundle\Model\ListModel;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class FieldModelTest extends MauticMysqlTestCase
|
||||
{
|
||||
protected $useCleanupRollback = false;
|
||||
|
||||
/**
|
||||
* @param array<string, mixed[]> $filters
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataForGetFieldsProperties')]
|
||||
public function testGetFieldsProperties(array $filters, int $expectedCount): void
|
||||
{
|
||||
/** @var FieldModel $fieldModel */
|
||||
$fieldModel = self::getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
// Create an unpublished lead field.
|
||||
$field = new LeadField();
|
||||
$field->setName('Test Unpublished Field')
|
||||
->setAlias('test_unpublished_field')
|
||||
->setType('string')
|
||||
->setObject('lead')
|
||||
->setIsPublished(false);
|
||||
|
||||
$fieldModel->saveEntity($field);
|
||||
|
||||
$fields = $fieldModel->getFieldsProperties($filters);
|
||||
|
||||
$this->assertCount($expectedCount, $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<string, mixed[]>
|
||||
*/
|
||||
public static function dataForGetFieldsProperties(): iterable
|
||||
{
|
||||
// When mautic is installed the total number of fields are 42.
|
||||
yield 'All fields' => [
|
||||
// Filters
|
||||
[],
|
||||
// Expected count
|
||||
44,
|
||||
];
|
||||
|
||||
yield 'Contact fields' => [
|
||||
// Filters
|
||||
['object' => 'lead'],
|
||||
// Expected count
|
||||
29,
|
||||
];
|
||||
|
||||
yield 'Company fields' => [
|
||||
// Filters
|
||||
['object' => 'company'],
|
||||
// Expected count
|
||||
15,
|
||||
];
|
||||
|
||||
yield 'Text fields' => [
|
||||
// Filters
|
||||
['type' => 'text'],
|
||||
// Expected count
|
||||
20,
|
||||
];
|
||||
|
||||
yield 'Unpublished fields' => [
|
||||
// Filters
|
||||
['isPublished' => false],
|
||||
// Expected count
|
||||
1,
|
||||
];
|
||||
}
|
||||
|
||||
public function testSingleContactFieldIsCreatedAndDeleted(): void
|
||||
{
|
||||
$fieldModel = static::getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
$field = new LeadField();
|
||||
$field->setName('Test Field')
|
||||
->setAlias('test_field')
|
||||
->setType('string')
|
||||
->setObject('lead');
|
||||
|
||||
$fieldModel->saveEntity($field);
|
||||
$fieldModel->deleteEntity($field);
|
||||
|
||||
$this->assertCount(0, $this->getColumns('leads', $field->getAlias()));
|
||||
}
|
||||
|
||||
public function testSingleCompanyFieldIsCreatedAndDeleted(): void
|
||||
{
|
||||
$fieldModel = static::getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
$field = new LeadField();
|
||||
$field->setName('Test Field')
|
||||
->setAlias('test_field')
|
||||
->setType('string')
|
||||
->setObject('company');
|
||||
|
||||
$fieldModel->saveEntity($field);
|
||||
$fieldModel->deleteEntity($field);
|
||||
|
||||
$this->assertCount(0, $this->getColumns('companies', $field->getAlias()));
|
||||
}
|
||||
|
||||
public function testMultipleFieldsAreCreatedAndDeleted(): void
|
||||
{
|
||||
$fieldModel = static::getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
$leadField = new LeadField();
|
||||
$leadField->setName('Test Field')
|
||||
->setAlias('test_field')
|
||||
->setType('string')
|
||||
->setObject('lead');
|
||||
|
||||
$leadField2 = new LeadField();
|
||||
$leadField2->setName('Test Field')
|
||||
->setAlias('test_field2')
|
||||
->setType('string')
|
||||
->setObject('lead');
|
||||
|
||||
$companyField = new LeadField();
|
||||
$companyField->setName('Test Field')
|
||||
->setAlias('test_field')
|
||||
->setType('string')
|
||||
->setObject('company');
|
||||
|
||||
$companyField2 = new LeadField();
|
||||
$companyField2->setName('Test Field')
|
||||
->setAlias('test_field2')
|
||||
->setType('string')
|
||||
->setObject('company');
|
||||
|
||||
$fieldModel->saveEntities([$leadField, $leadField2, $companyField, $companyField2]);
|
||||
|
||||
$this->assertCount(1, $this->getColumns('leads', $leadField->getAlias()));
|
||||
$this->assertCount(1, $this->getColumns('leads', $leadField2->getAlias()));
|
||||
$this->assertCount(1, $this->getColumns('companies', $companyField->getAlias()));
|
||||
$this->assertCount(1, $this->getColumns('companies', $companyField2->getAlias()));
|
||||
|
||||
$fieldModel->deleteEntities([$leadField->getId(), $leadField2->getId(), $companyField->getId(), $companyField2->getId()]);
|
||||
|
||||
$this->assertCount(0, $this->getColumns('leads', $leadField->getAlias()));
|
||||
$this->assertCount(0, $this->getColumns('leads', $leadField2->getAlias()));
|
||||
$this->assertCount(0, $this->getColumns('companies', $companyField->getAlias()));
|
||||
$this->assertCount(0, $this->getColumns('companies', $companyField2->getAlias()));
|
||||
}
|
||||
|
||||
public function testGenerateUniqueFieldAlias(): void
|
||||
{
|
||||
$repoMock = $this->getMockBuilder(LeadFieldRepository::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['__call']) // only __call can intercept dynamic methods
|
||||
->getMock();
|
||||
|
||||
$repoMock->method('__call')
|
||||
->with('findOneByAlias', $this->anything())
|
||||
->willReturnCallback(function ($method, $args) {
|
||||
$alias = $args[0];
|
||||
|
||||
// Simulate alias and alias_1 are taken, alias_2 is available
|
||||
if (in_array($alias, ['alias', 'alias_1'], true)) {
|
||||
return new LeadField();
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
// Anonymous subclass that overrides getRepository
|
||||
$fieldModel = new FieldModel(
|
||||
$this->createMock(ColumnSchemaHelper::class),
|
||||
$this->createMock(ListModel::class),
|
||||
$this->createMock(CustomFieldColumn::class),
|
||||
$this->createMock(FieldSaveDispatcher::class),
|
||||
$repoMock,
|
||||
$this->createMock(FieldList::class),
|
||||
$this->createMock(LeadFieldSaver::class),
|
||||
$this->createMock(LeadFieldDeleter::class),
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(CoreParametersHelper::class),
|
||||
);
|
||||
|
||||
$result = $fieldModel->generateUniqueFieldAlias('alias');
|
||||
$this->assertEquals('alias_2', $result);
|
||||
}
|
||||
|
||||
public function testIsUsedField(): void
|
||||
{
|
||||
$leadField = new LeadField();
|
||||
|
||||
$columnSchemaHelper = $this->createMock(ColumnSchemaHelper::class);
|
||||
$leadListModel = $this->createMock(ListModel::class);
|
||||
$customFieldColumn = $this->createMock(CustomFieldColumn::class);
|
||||
$fieldSaveDispatcher = $this->createMock(FieldSaveDispatcher::class);
|
||||
$leadFieldRepository = $this->createMock(LeadFieldRepository::class);
|
||||
$fieldList = $this->createMock(FieldList::class);
|
||||
$leadFieldSaver = $this->createMock(LeadFieldSaver::class);
|
||||
$leadFieldDeleter = $this->createMock(LeadFieldDeleter::class);
|
||||
$leadListModel->expects($this->once())
|
||||
->method('isFieldUsed')
|
||||
->with($leadField)
|
||||
->willReturn(true);
|
||||
|
||||
$model = new FieldModel(
|
||||
$columnSchemaHelper,
|
||||
$leadListModel,
|
||||
$customFieldColumn,
|
||||
$fieldSaveDispatcher,
|
||||
$leadFieldRepository,
|
||||
$fieldList,
|
||||
$leadFieldSaver,
|
||||
$leadFieldDeleter,
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class),
|
||||
$this->createMock(CoreParametersHelper::class),
|
||||
);
|
||||
$this->assertTrue($model->isUsedField($leadField));
|
||||
}
|
||||
|
||||
public function testUniqueIdentifierIndexToggleForContacts(): void
|
||||
{
|
||||
// Log queries so we can detect if alter queries were executed
|
||||
/** $stack */
|
||||
$stack = new class implements SQLLogger { /** @phpstan-ignore-line SQLLogger is deprecated */
|
||||
/** @var array<mixed> */
|
||||
private array $indexQueries = [];
|
||||
|
||||
public function startQuery($sql, ?array $params = null, ?array $types = null)
|
||||
{
|
||||
if (false !== stripos($sql, 'create index')) {
|
||||
$this->indexQueries[] = $sql;
|
||||
}
|
||||
|
||||
if (false !== stripos($sql, 'drop index')) {
|
||||
$this->indexQueries[] = $sql;
|
||||
}
|
||||
}
|
||||
|
||||
public function stopQuery()
|
||||
{
|
||||
// not used
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getIndexQueries(): array
|
||||
{
|
||||
return $this->indexQueries;
|
||||
}
|
||||
|
||||
public function resetQueries(): void
|
||||
{
|
||||
$this->indexQueries = [];
|
||||
}
|
||||
};
|
||||
|
||||
$this->connection->getConfiguration()->setSQLLogger($stack); /** @phpstan-ignore-line SQLLogger is deprecated */
|
||||
$fieldModel = $this->getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
// Ensure the index exists
|
||||
$emailField = $fieldModel->getEntityByAlias('email');
|
||||
$fieldModel->saveEntity($emailField);
|
||||
$columns = $this->getUniqueIdentifierIndexColumns('leads');
|
||||
Assert::assertCount(1, $columns);
|
||||
Assert::assertEquals('email', $columns[0]['COLUMN_NAME']);
|
||||
$stack->resetQueries();
|
||||
|
||||
// Test updating the index
|
||||
$ui1Field = new LeadField();
|
||||
$ui1Field->setName('UI1')
|
||||
->setAlias('ui1')
|
||||
->setType('string')
|
||||
->setObject('lead')
|
||||
->setIsUniqueIdentifier(true);
|
||||
$fieldModel->saveEntity($ui1Field);
|
||||
$columns = $this->getUniqueIdentifierIndexColumns('leads');
|
||||
Assert::assertCount(2, $columns);
|
||||
Assert::assertEquals('email', $columns[0]['COLUMN_NAME']);
|
||||
Assert::assertEquals('ui1', $columns[1]['COLUMN_NAME']);
|
||||
$alteredIndexes = $stack->getIndexQueries();
|
||||
Assert::assertCount(3, $alteredIndexes);
|
||||
Assert::assertEquals(sprintf('DROP INDEX %1$sunique_identifier_search ON %1$sleads', MAUTIC_TABLE_PREFIX), $alteredIndexes[0]);
|
||||
Assert::assertEquals(sprintf('CREATE INDEX %1$sunique_identifier_search ON %1$sleads (email, ui1)', MAUTIC_TABLE_PREFIX), $alteredIndexes[1]);
|
||||
Assert::assertEquals(sprintf('CREATE INDEX %1$sui1_search ON %1$sleads (ui1)', MAUTIC_TABLE_PREFIX), $alteredIndexes[2]);
|
||||
$stack->resetQueries();
|
||||
|
||||
// Test only the first 3 columns are used for the index
|
||||
$ui2Field = new LeadField();
|
||||
$ui2Field->setName('UI2')
|
||||
->setAlias('ui2')
|
||||
->setType('string')
|
||||
->setObject('lead')
|
||||
->setIsUniqueIdentifier(true);
|
||||
$ui3Field = new LeadField();
|
||||
$ui3Field->setName('UI3')
|
||||
->setAlias('ui3')
|
||||
->setType('string')
|
||||
->setObject('lead')
|
||||
->setIsUniqueIdentifier(true);
|
||||
$fieldModel->saveEntities([$ui2Field, $ui3Field]);
|
||||
$columns = $this->getUniqueIdentifierIndexColumns('leads');
|
||||
Assert::assertCount(3, $columns);
|
||||
Assert::assertEquals('email', $columns[0]['COLUMN_NAME']);
|
||||
Assert::assertEquals('ui1', $columns[1]['COLUMN_NAME']);
|
||||
Assert::assertEquals('ui2', $columns[2]['COLUMN_NAME']);
|
||||
$alteredIndexes = $stack->getIndexQueries();
|
||||
Assert::assertCount(4, $alteredIndexes);
|
||||
Assert::assertEquals(sprintf('DROP INDEX %1$sunique_identifier_search ON %1$sleads', MAUTIC_TABLE_PREFIX), $alteredIndexes[0]);
|
||||
Assert::assertEquals(
|
||||
sprintf('CREATE INDEX %1$sunique_identifier_search ON %1$sleads (email, ui1, ui2)', MAUTIC_TABLE_PREFIX),
|
||||
$alteredIndexes[1]
|
||||
);
|
||||
Assert::assertEquals(sprintf('CREATE INDEX %1$sui2_search ON %1$sleads (ui2)', MAUTIC_TABLE_PREFIX), $alteredIndexes[2]);
|
||||
Assert::assertEquals(sprintf('CREATE INDEX %1$sui3_search ON %1$sleads (ui3)', MAUTIC_TABLE_PREFIX), $alteredIndexes[3]);
|
||||
$stack->resetQueries();
|
||||
|
||||
// Test that the index was not touched if only the label was updated
|
||||
$ui1Field->setLabel('UI1 Patched Again');
|
||||
$fieldModel->saveEntity($ui1Field);
|
||||
$columns = $this->getUniqueIdentifierIndexColumns('leads');
|
||||
Assert::assertCount(3, $columns);
|
||||
Assert::assertCount(0, $stack->getIndexQueries());
|
||||
|
||||
// Cleanup
|
||||
$fieldModel->deleteEntities([$ui1Field->getId(), $ui2Field->getId(), $ui3Field->getId()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function getColumns(string $table, string $column): array
|
||||
{
|
||||
$stmt = $this->connection->executeQuery(
|
||||
"SELECT * FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = '{$this->connection->getDatabase()}' AND TABLE_NAME = '"
|
||||
.MAUTIC_TABLE_PREFIX
|
||||
."$table' AND COLUMN_NAME = '$column'"
|
||||
);
|
||||
|
||||
return $stmt->fetchAllAssociative();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function getUniqueIdentifierIndexColumns(string $table): array
|
||||
{
|
||||
$stmt = $this->connection->executeQuery(
|
||||
sprintf(
|
||||
"SELECT * FROM information_schema.statistics where table_schema = '%s' and table_name = '%s' and index_name = '%sunique_identifier_search'",
|
||||
$this->connection->getDatabase(),
|
||||
MAUTIC_TABLE_PREFIX.$table,
|
||||
MAUTIC_TABLE_PREFIX
|
||||
)
|
||||
);
|
||||
|
||||
return $stmt->fetchAllAssociative();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,533 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\Exception\ORMException;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Model\NotificationModel;
|
||||
use Mautic\CoreBundle\ProcessSignal\ProcessSignalService;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\LeadBundle\Entity\Import;
|
||||
use Mautic\LeadBundle\Entity\ImportRepository;
|
||||
use Mautic\LeadBundle\Entity\LeadEventLog;
|
||||
use Mautic\LeadBundle\Entity\LeadEventLogRepository;
|
||||
use Mautic\LeadBundle\Event\ImportProcessEvent;
|
||||
use Mautic\LeadBundle\Exception\ImportDelayedException;
|
||||
use Mautic\LeadBundle\Exception\ImportFailedException;
|
||||
use Mautic\LeadBundle\Helper\Progress;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Mautic\LeadBundle\Model\ImportModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\LeadBundle\Tests\StandardImportTestHelper;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class ImportModelTest extends StandardImportTestHelper
|
||||
{
|
||||
public function testInitEventLog(): void
|
||||
{
|
||||
$userId = 4;
|
||||
$userName = 'John Doe';
|
||||
$fileName = 'import.csv';
|
||||
$line = 104;
|
||||
$model = $this->initImportModel();
|
||||
$entity = $this->initImportEntity();
|
||||
$entity->setCreatedBy($userId)
|
||||
->setCreatedByUser($userName)
|
||||
->setModifiedBy($userId)
|
||||
->setModifiedByUser($userName)
|
||||
->setOriginalFile($fileName);
|
||||
$log = $model->initEventLog($entity, $line);
|
||||
|
||||
Assert::assertSame($userId, $log->getUserId());
|
||||
Assert::assertSame($userName, $log->getUserName());
|
||||
Assert::assertSame('lead', $log->getBundle());
|
||||
Assert::assertSame('import', $log->getObject());
|
||||
Assert::assertSame(['line' => $line, 'file' => $fileName], $log->getProperties());
|
||||
}
|
||||
|
||||
public function testProcess(): void
|
||||
{
|
||||
$model = $this->initImportModel();
|
||||
$entity = $this->initImportEntity();
|
||||
|
||||
$this->dispatcher->expects($this->exactly(4))
|
||||
->method('dispatch')
|
||||
->with(
|
||||
$this->callback(function (ImportProcessEvent $event) {
|
||||
// Emulate a subscriber.
|
||||
$event->setWasMerged(false);
|
||||
|
||||
return true;
|
||||
}),
|
||||
LeadEvents::IMPORT_ON_PROCESS
|
||||
);
|
||||
|
||||
$entity->start();
|
||||
$model->process($entity, new Progress());
|
||||
$entity->end();
|
||||
|
||||
Assert::assertEquals(100, $entity->getProgressPercentage());
|
||||
Assert::assertSame(4, $entity->getInsertedCount());
|
||||
Assert::assertSame(2, $entity->getIgnoredCount());
|
||||
Assert::assertSame(Import::IMPORTED, $entity->getStatus());
|
||||
}
|
||||
|
||||
public function testCheckParallelImportLimitWhenMore(): void
|
||||
{
|
||||
$model = $this->getMockBuilder(ImportModel::class)
|
||||
->onlyMethods(['getParallelImportLimit', 'getRepository'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getParallelImportLimit')
|
||||
->willReturn(4);
|
||||
|
||||
$repository = $this->getMockBuilder(ImportRepository::class)
|
||||
->onlyMethods(['countImportsWithStatuses'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$repository->expects($this->once())
|
||||
->method('countImportsWithStatuses')
|
||||
->willReturn(5);
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getRepository')
|
||||
->willReturn($repository);
|
||||
|
||||
$result = $model->checkParallelImportLimit();
|
||||
|
||||
Assert::assertFalse($result);
|
||||
}
|
||||
|
||||
public function testCheckParallelImportLimitWhenEqual(): void
|
||||
{
|
||||
$model = $this->getMockBuilder(ImportModel::class)
|
||||
->onlyMethods(['getParallelImportLimit', 'getRepository'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getParallelImportLimit')
|
||||
->willReturn(4);
|
||||
|
||||
$repository = $this->getMockBuilder(ImportRepository::class)
|
||||
->onlyMethods(['countImportsWithStatuses'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$repository->expects($this->once())
|
||||
->method('countImportsWithStatuses')
|
||||
->willReturn(4);
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getRepository')
|
||||
->willReturn($repository);
|
||||
|
||||
$result = $model->checkParallelImportLimit();
|
||||
|
||||
Assert::assertFalse($result);
|
||||
}
|
||||
|
||||
public function testCheckParallelImportLimitWhenLess(): void
|
||||
{
|
||||
$model = $this->getMockBuilder(ImportModel::class)
|
||||
->onlyMethods(['getParallelImportLimit', 'getRepository'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getParallelImportLimit')
|
||||
->willReturn(6);
|
||||
|
||||
$repository = $this->getMockBuilder(ImportRepository::class)
|
||||
->onlyMethods(['countImportsWithStatuses'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$repository->expects($this->once())
|
||||
->method('countImportsWithStatuses')
|
||||
->willReturn(5);
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getRepository')
|
||||
->willReturn($repository);
|
||||
|
||||
$result = $model->checkParallelImportLimit();
|
||||
|
||||
Assert::assertTrue($result);
|
||||
}
|
||||
|
||||
public function testBeginImportWhenParallelLimitHit(): void
|
||||
{
|
||||
$model = $this->getMockBuilder(\Mautic\LeadBundle\Tests\Fixtures\Model\ImportModel::class)
|
||||
->onlyMethods(['checkParallelImportLimit', 'setGhostImportsAsFailed', 'saveEntity', 'getParallelImportLimit'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$model->setTranslator($this->getTranslatorMock());
|
||||
|
||||
$model->method('checkParallelImportLimit')
|
||||
->willReturn(false);
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('getParallelImportLimit')
|
||||
->willReturn(1);
|
||||
|
||||
$entity = $this->initImportEntity(['canProceed']);
|
||||
|
||||
$entity->method('canProceed')
|
||||
->willReturn(true);
|
||||
|
||||
try {
|
||||
$model->beginImport($entity, new Progress());
|
||||
$this->fail();
|
||||
} catch (ImportDelayedException) {
|
||||
// This is expected
|
||||
}
|
||||
|
||||
Assert::assertEquals(0, $entity->getProgressPercentage());
|
||||
Assert::assertSame(0, $entity->getInsertedCount());
|
||||
Assert::assertSame(0, $entity->getIgnoredCount());
|
||||
Assert::assertSame(Import::DELAYED, $entity->getStatus());
|
||||
|
||||
$model->expects($this->never())->method('saveEntity');
|
||||
}
|
||||
|
||||
public function testBeginImportWhenDatabaseException(): void
|
||||
{
|
||||
$model = $this->getMockBuilder(\Mautic\LeadBundle\Tests\Fixtures\Model\ImportModel::class)
|
||||
->onlyMethods(['checkParallelImportLimit', 'setGhostImportsAsFailed', 'saveEntity', 'logDebug', 'process'])
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$model->setTranslator($this->getTranslatorMock());
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('checkParallelImportLimit')
|
||||
->willReturn(true);
|
||||
|
||||
$model->expects($this->once())
|
||||
->method('process')
|
||||
->will($this->throwException(new ORMException()));
|
||||
|
||||
$entity = $this->initImportEntity(['canProceed']);
|
||||
|
||||
$entity->method('canProceed')
|
||||
->willReturn(true);
|
||||
|
||||
try {
|
||||
$model->beginImport($entity, new Progress());
|
||||
$this->fail();
|
||||
} catch (ImportFailedException) {
|
||||
// This is expected
|
||||
}
|
||||
|
||||
Assert::assertEquals(0, $entity->getProgressPercentage());
|
||||
Assert::assertSame(0, $entity->getInsertedCount());
|
||||
Assert::assertSame(0, $entity->getIgnoredCount());
|
||||
Assert::assertSame(Import::DELAYED, $entity->getStatus());
|
||||
|
||||
$model->expects($this->never())->method('saveEntity');
|
||||
}
|
||||
|
||||
public function testIsEmptyCsvRow(): void
|
||||
{
|
||||
$model = $this->initImportModel();
|
||||
$testData = [
|
||||
[
|
||||
'row' => '',
|
||||
'res' => true,
|
||||
],
|
||||
[
|
||||
'row' => [],
|
||||
'res' => true,
|
||||
],
|
||||
[
|
||||
'row' => [null],
|
||||
'res' => true,
|
||||
],
|
||||
[
|
||||
'row' => [''],
|
||||
'res' => true,
|
||||
],
|
||||
[
|
||||
'row' => ['John'],
|
||||
'res' => false,
|
||||
],
|
||||
[
|
||||
'row' => ['John', 'Doe'],
|
||||
'res' => false,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($testData as $test) {
|
||||
Assert::assertSame(
|
||||
$test['res'],
|
||||
$model->isEmptyCsvRow($test['row']),
|
||||
'Failed on row '.var_export($test['row'], true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testTrimArrayValues(): void
|
||||
{
|
||||
$model = $this->initImportModel();
|
||||
$testData = [
|
||||
[
|
||||
'row' => ['John '],
|
||||
'res' => ['John'],
|
||||
],
|
||||
[
|
||||
'row' => [' John ', ' Do e '],
|
||||
'res' => ['John', 'Do e'],
|
||||
],
|
||||
[
|
||||
'row' => ['key' => ' John ', 2 => ' Do e '],
|
||||
'res' => ['key' => 'John', 2 => 'Do e'],
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($testData as $test) {
|
||||
Assert::assertSame(
|
||||
$test['res'],
|
||||
$model->trimArrayValues($test['row']),
|
||||
'Failed on row '.var_export($test['row'], true)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testHasMoreValuesThanColumns(): void
|
||||
{
|
||||
$model = $this->initImportModel();
|
||||
$columns = 3;
|
||||
$testData = [
|
||||
[
|
||||
'row' => ['John'],
|
||||
'mod' => ['John', '', ''],
|
||||
'res' => false,
|
||||
],
|
||||
[
|
||||
'row' => ['John', 'Doe'],
|
||||
'mod' => ['John', 'Doe', ''],
|
||||
'res' => false,
|
||||
],
|
||||
[
|
||||
'row' => ['key' => 'John', 2 => 'Doe', 'stuff'],
|
||||
'mod' => ['key' => 'John', 2 => 'Doe', 'stuff'],
|
||||
'res' => false,
|
||||
],
|
||||
[
|
||||
'row' => ['key' => 'John', 2 => 'Doe', 'stuff', 'this is too much'],
|
||||
'mod' => ['key' => 'John', 2 => 'Doe', 'stuff', 'this is too much'],
|
||||
'res' => true,
|
||||
],
|
||||
];
|
||||
|
||||
foreach ($testData as $test) {
|
||||
$res = $model->hasMoreValuesThanColumns($test['row'], $columns);
|
||||
Assert::assertSame(
|
||||
$test['res'],
|
||||
$res,
|
||||
'Failed on row '.var_export($test['row'], true)
|
||||
);
|
||||
Assert::assertSame($test['mod'], $test['row']);
|
||||
}
|
||||
}
|
||||
|
||||
public function testLimit(): void
|
||||
{
|
||||
$model = $this->initImportModel();
|
||||
|
||||
$import = new Import();
|
||||
$import->setFilePath(self::$largeCsvPath)
|
||||
->setLineCount(511)
|
||||
->setHeaders(self::$initialList[0])
|
||||
->setParserConfig(
|
||||
[
|
||||
'batchlimit' => 10,
|
||||
'delimiter' => ',',
|
||||
'enclosure' => '"',
|
||||
'escape' => '/',
|
||||
]
|
||||
);
|
||||
|
||||
$import->start();
|
||||
$progress = new Progress();
|
||||
// Each batch should have the last line imported recorded as limit + 1
|
||||
$model->process($import, $progress, 100);
|
||||
Assert::assertEquals(101, $import->getLastLineImported());
|
||||
$model->process($import, $progress, 100);
|
||||
Assert::assertEquals(201, $import->getLastLineImported());
|
||||
$model->process($import, $progress, 100);
|
||||
Assert::assertEquals(301, $import->getLastLineImported());
|
||||
$model->process($import, $progress, 100);
|
||||
Assert::assertEquals(401, $import->getLastLineImported());
|
||||
$model->process($import, $progress, 100);
|
||||
Assert::assertEquals(501, $import->getLastLineImported());
|
||||
$model->process($import, $progress, 100);
|
||||
|
||||
// 512 is an empty line in the CSV
|
||||
Assert::assertEquals(512, $import->getLastLineImported());
|
||||
|
||||
// Excluding the header but including the empty row in 512, there are 511 rows
|
||||
Assert::assertEquals(511, $import->getProcessedRows());
|
||||
|
||||
$import->end();
|
||||
}
|
||||
|
||||
public function testItLogsDBErrorIfTheEntityManagerIsClosed(): void
|
||||
{
|
||||
$this->generateSmallCSV();
|
||||
|
||||
$importModel = $this->initImportModel(false);
|
||||
$import = $this->initImportEntity();
|
||||
|
||||
$this->expectException(ORMException::class);
|
||||
$this->dispatcher->expects($this->once())
|
||||
->method('dispatch')
|
||||
->willThrowException(new ORMException('Some DB error'));
|
||||
|
||||
$import->start();
|
||||
$importModel->process($import, new Progress());
|
||||
$import->end();
|
||||
|
||||
Assert::assertSame(Import::FAILED, $import->getStatus());
|
||||
}
|
||||
|
||||
public function testWhenWarningsAvailableInProcessEventLog(): void
|
||||
{
|
||||
$model = $this->initImportModel();
|
||||
$entity = $this->initImportEntity();
|
||||
|
||||
$this->dispatcher->expects($this->exactly(4))
|
||||
->method('dispatch')
|
||||
->with(
|
||||
$this->callback(function (ImportProcessEvent $event) {
|
||||
// Emulate a subscriber.
|
||||
$event->setWasMerged(false);
|
||||
$event->addWarning('test warning message');
|
||||
|
||||
return true;
|
||||
}),
|
||||
LeadEvents::IMPORT_ON_PROCESS
|
||||
);
|
||||
|
||||
$entity->start();
|
||||
$model->process($entity, new Progress());
|
||||
$entity->end();
|
||||
|
||||
Assert::assertEquals(100, $entity->getProgressPercentage());
|
||||
Assert::assertSame(Import::IMPORTED, $entity->getStatus());
|
||||
}
|
||||
|
||||
public function testWhenImportUnpublishedInBetweenImportProcess(): void
|
||||
{
|
||||
$translator = $this->getTranslatorMock();
|
||||
$pathsHelper = $this->getPathsHelperMock();
|
||||
$this->entityManager = $this->getEntityManagerMock();
|
||||
$coreParametersHelper = $this->getCoreParametersHelperMock();
|
||||
|
||||
/** @var MockObject&UserHelper */
|
||||
$userHelper = $this->createMock(UserHelper::class);
|
||||
|
||||
/** @var MockObject&LeadEventLogRepository */
|
||||
$logRepository = $this->createMock(LeadEventLogRepository::class);
|
||||
|
||||
/** @var MockObject&ImportRepository */
|
||||
$importRepository = $this->createMock(ImportRepository::class);
|
||||
|
||||
$importRepository->expects($this->exactly(3))->method('getValue')
|
||||
->willReturnOnConsecutiveCalls(true, false, false);
|
||||
|
||||
$this->entityManager->expects($this->any())
|
||||
->method('getRepository')
|
||||
->willReturnMap(
|
||||
[
|
||||
[LeadEventLog::class, $logRepository],
|
||||
[Import::class, $importRepository],
|
||||
]
|
||||
);
|
||||
|
||||
$this->entityManager->expects($this->any())
|
||||
->method('isOpen')
|
||||
->willReturn(true);
|
||||
|
||||
/** @var MockObject&LeadModel $leadModel */
|
||||
$leadModel = $this->getMockBuilder(LeadModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->setConstructorArgs([16 => $this->entityManager])
|
||||
->getMock();
|
||||
|
||||
$leadModel->expects($this->any())
|
||||
->method('getEventLogRepository')
|
||||
->willReturn($logRepository);
|
||||
|
||||
/** @var MockObject&CompanyModel $companyModel */
|
||||
$companyModel = $this->getMockBuilder(CompanyModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->setConstructorArgs([3 => $this->entityManager])
|
||||
->getMock();
|
||||
|
||||
/** @var MockObject&NotificationModel $notificationModel */
|
||||
$notificationModel = $this->getMockBuilder(NotificationModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->setConstructorArgs([3 => $this->entityManager])
|
||||
->getMock();
|
||||
|
||||
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
|
||||
$this->dispatcher->expects($this->exactly(4))
|
||||
->method('dispatch')
|
||||
->with(
|
||||
$this->callback(function (ImportProcessEvent $event) {
|
||||
// Emulate a subscriber.
|
||||
$event->setWasMerged(false);
|
||||
|
||||
return true;
|
||||
}),
|
||||
LeadEvents::IMPORT_ON_PROCESS,
|
||||
);
|
||||
|
||||
$importModel = new ImportModel(
|
||||
$pathsHelper,
|
||||
$leadModel,
|
||||
$notificationModel,
|
||||
$coreParametersHelper,
|
||||
$companyModel,
|
||||
$this->entityManager,
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->dispatcher,
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$translator,
|
||||
$userHelper,
|
||||
$this->createMock(LoggerInterface::class),
|
||||
new ProcessSignalService()
|
||||
);
|
||||
|
||||
$this->setUpBeforeClass();
|
||||
|
||||
$entity = $this->initImportEntity();
|
||||
$entity->setParserConfig([
|
||||
'batchlimit' => 3,
|
||||
'delimiter' => ',',
|
||||
'enclosure' => '"',
|
||||
'escape' => '/',
|
||||
]);
|
||||
|
||||
$entity->start();
|
||||
$importModel->process($entity, new Progress());
|
||||
$entity->end();
|
||||
|
||||
Assert::assertSame(4, $entity->getInsertedCount());
|
||||
Assert::assertSame(Import::STOPPED, $entity->getStatus());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Doctrine\Common\Collections\ArrayCollection;
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Doctrine\DBAL\Exception\DriverException;
|
||||
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\IpAddressModel;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class IpAddressModelTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var EntityManager|MockObject
|
||||
*/
|
||||
private MockObject $entityManager;
|
||||
|
||||
/**
|
||||
* @var MockObject|LoggerInterface
|
||||
*/
|
||||
private MockObject $logger;
|
||||
|
||||
private IpAddressModel $ipAddressModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->entityManager = $this->createMock(EntityManager::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->ipAddressModel = new IpAddressModel($this->entityManager, $this->logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* This test ensures it won't fail if there are no IP addresses.
|
||||
*/
|
||||
public function testSaveIpAddressReferencesForContactWhenNoIps(): void
|
||||
{
|
||||
$this->entityManager->expects($this->never())
|
||||
->method('getConnection');
|
||||
|
||||
$this->ipAddressModel->saveIpAddressesReferencesForContact(new Lead());
|
||||
}
|
||||
|
||||
public function testSaveIpAddressReferencesForContactThatHasIpsButNoChanges(): void
|
||||
{
|
||||
$contact = $this->createMock(Lead::class);
|
||||
$ipAddress = $this->createMock(IpAddress::class);
|
||||
$ipAddresses = new ArrayCollection(['1.2.3.4' => $ipAddress]);
|
||||
$connection = $this->createMock(Connection::class);
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
|
||||
$contact->expects($this->exactly(1))
|
||||
->method('getIpAddresses')
|
||||
->willReturn($ipAddresses);
|
||||
|
||||
$this->entityManager->expects($this->never())
|
||||
->method('getConnection');
|
||||
|
||||
$this->ipAddressModel->saveIpAddressesReferencesForContact($contact);
|
||||
}
|
||||
|
||||
public function testSaveIpAddressReferencesForContactThatHasIpsWithSomeAdded(): void
|
||||
{
|
||||
$contact = $this->createMock(Lead::class);
|
||||
$ipAddressAdded = $this->createMock(IpAddress::class);
|
||||
$ipAddressOld = $this->createMock(IpAddress::class);
|
||||
$ipAddresses = new ArrayCollection(['1.2.3.999' => $ipAddressOld, '1.2.3.4' => $ipAddressAdded]);
|
||||
$connection = $this->createMock(Connection::class);
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getIpAddresses')
|
||||
->willReturn($ipAddresses);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getId')
|
||||
->willReturn(55);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getChanges')
|
||||
->willReturn(['ipAddressList' => ['1.2.3.4' => $ipAddressAdded]]);
|
||||
|
||||
$ipAddressAdded->expects($this->exactly(2))
|
||||
->method('getId')
|
||||
->willReturn(44);
|
||||
|
||||
$ipAddressAdded->expects($this->once())
|
||||
->method('getIpAddress')
|
||||
->willReturn('1.2.3.4');
|
||||
|
||||
$ipAddressOld->expects($this->never())
|
||||
->method('getId');
|
||||
|
||||
$ipAddressOld->expects($this->once())
|
||||
->method('getIpAddress')
|
||||
->willReturn('1.2.3.999');
|
||||
|
||||
$queryBuilder->expects($this->once())
|
||||
->method('executeStatement');
|
||||
|
||||
$connection->expects($this->once())
|
||||
->method('createQueryBuilder')
|
||||
->willReturn($queryBuilder);
|
||||
|
||||
$this->entityManager->expects($this->once())
|
||||
->method('getConnection')
|
||||
->willReturn($connection);
|
||||
|
||||
$this->ipAddressModel->saveIpAddressesReferencesForContact($contact);
|
||||
|
||||
$this->assertCount(2, $contact->getIpAddresses());
|
||||
}
|
||||
|
||||
public function testSaveIpAddressReferencesForContactWhenSomeIpsIfTheReferenceExistsAlready(): void
|
||||
{
|
||||
$contact = $this->createMock(Lead::class);
|
||||
$ipAddress = $this->createMock(IpAddress::class);
|
||||
$ipAddresses = new ArrayCollection(['1.2.3.4' => $ipAddress]);
|
||||
$connection = $this->createMock(Connection::class);
|
||||
$queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getIpAddresses')
|
||||
->willReturn($ipAddresses);
|
||||
|
||||
$contact->expects($this->exactly(3))
|
||||
->method('getId')
|
||||
->willReturn(55);
|
||||
|
||||
$contact->expects($this->once())
|
||||
->method('getChanges')
|
||||
->willReturn(['ipAddressList' => ['1.2.3.4' => $ipAddress]]);
|
||||
|
||||
$ipAddress->expects($this->exactly(3))
|
||||
->method('getId')
|
||||
->willReturn(44);
|
||||
|
||||
$ipAddress->expects($this->once())
|
||||
->method('getIpAddress')
|
||||
->willReturn('1.2.3.4');
|
||||
|
||||
$queryBuilder->expects($this->once())
|
||||
->method('executeStatement')
|
||||
->willThrowException(new UniqueConstraintViolationException($this->createMock(DriverException::class), null));
|
||||
|
||||
$connection->expects($this->once())
|
||||
->method('createQueryBuilder')
|
||||
->willReturn($queryBuilder);
|
||||
|
||||
$this->entityManager->expects($this->once())
|
||||
->method('getConnection')
|
||||
->willReturn($connection);
|
||||
|
||||
$this->ipAddressModel->saveIpAddressesReferencesForContact($contact);
|
||||
|
||||
$this->assertCount(1, $contact->getIpAddresses());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Helper\Serializer;
|
||||
use Mautic\LeadBundle\Entity\LeadList;
|
||||
use Mautic\LeadBundle\Model\ListModel;
|
||||
|
||||
class LeadListModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
protected $fixture;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$mockListModel = $this->getMockBuilder(ListModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getEntities', 'getEntity'])
|
||||
->getMock();
|
||||
|
||||
$mockListModel->expects($this->any())
|
||||
->method('getEntity')
|
||||
->willReturnCallback(function ($id) {
|
||||
$mockEntity = $this->getMockBuilder(LeadList::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getName'])
|
||||
->getMock();
|
||||
|
||||
$mockEntity->expects($this->once())
|
||||
->method('getName')
|
||||
->willReturn((string) $id);
|
||||
|
||||
return $mockEntity;
|
||||
});
|
||||
|
||||
$filters = 'a:1:{i:0;a:7:{s:4:"glue";s:3:"and";s:5:"field";s:8:"leadlist";s:6:"object";s:4:"lead";s:4:"type";s:8:"leadlist";s:6:"filter";a:2:{i:0;i:1;i:1;i:3;}s:7:"display";N;s:8:"operator";s:2:"in";}}';
|
||||
|
||||
$filters4 = 'a:1:{i:0;a:7:{s:4:"glue";s:3:"and";s:5:"field";s:8:"leadlist";s:6:"object";s:4:"lead";s:4:"type";s:8:"leadlist";s:6:"filter";a:1:{i:0;i:3;}s:7:"display";N;s:8:"operator";s:2:"in";}}';
|
||||
|
||||
$mockEntity = $this->createMock(LeadList::class);
|
||||
|
||||
$mockEntity1 = clone $mockEntity;
|
||||
$mockEntity1->expects($this->once())
|
||||
->method('getFilters')
|
||||
->willReturn([]);
|
||||
$mockEntity1->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$mockEntity2 = clone $mockEntity;
|
||||
$mockEntity2->expects($this->once())
|
||||
->method('getFilters')
|
||||
->willReturn(Serializer::decode($filters));
|
||||
$mockEntity2->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(2);
|
||||
|
||||
$mockEntity3 = clone $mockEntity;
|
||||
$mockEntity3->expects($this->once())
|
||||
->method('getFilters')
|
||||
->willReturn([]);
|
||||
$mockEntity3->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(3);
|
||||
|
||||
$mockEntity4 = clone $mockEntity;
|
||||
$mockEntity4->expects($this->once())
|
||||
->method('getFilters')
|
||||
->willReturn(Serializer::decode($filters4));
|
||||
$mockEntity4->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(4);
|
||||
|
||||
$mockListModel->expects($this->once())
|
||||
->method('getEntities')
|
||||
->willReturn([
|
||||
1 => $mockEntity1,
|
||||
2 => $mockEntity2,
|
||||
3 => $mockEntity3,
|
||||
4 => $mockEntity4,
|
||||
]);
|
||||
|
||||
$this->fixture = $mockListModel;
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('segmentTestDataProvider')]
|
||||
public function testSegmentsCanBeDeletedCorrecty(array $arg, array $expected, $message): void
|
||||
{
|
||||
$result = $this->fixture->canNotBeDeleted($arg);
|
||||
|
||||
$this->assertEquals($expected, $result, $message);
|
||||
}
|
||||
|
||||
public static function segmentTestDataProvider()
|
||||
{
|
||||
return [
|
||||
[
|
||||
[1],
|
||||
[1 => '1'],
|
||||
'2 is dependent on 1, so 1 cannot be deleted.',
|
||||
],
|
||||
[
|
||||
[1, 3],
|
||||
[1 => '1', 3 => '3'],
|
||||
'2 is dependent on 1 & 3, so 1 & 3 cannot be deleted.',
|
||||
],
|
||||
[
|
||||
[1, 2, 3, 4],
|
||||
[],
|
||||
'Since we are deleting all segments, it should not prevent any from being deleted.',
|
||||
],
|
||||
[
|
||||
[2],
|
||||
[],
|
||||
'Segments without any other segment dependent on them should always be able to be deleted.',
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,259 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Doctrine\DBAL\Exception as DBALException;
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\LeadBundle\Entity\Company;
|
||||
use Mautic\LeadBundle\Entity\CompanyLead;
|
||||
use Mautic\LeadBundle\Entity\CompanyLeadRepository;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadField;
|
||||
use Mautic\LeadBundle\Event\LeadEvent;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher;
|
||||
|
||||
class LeadModelFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
private $pointsAdded = false;
|
||||
|
||||
protected $useCleanupRollback = false;
|
||||
|
||||
public function testSavingPrimaryCompanyAfterPointsAreSetByListenerAreNotResetToDefaultOf0BecauseOfPointsFieldDefaultIs0(): void
|
||||
{
|
||||
/** @var EventDispatcher $eventDispatcher */
|
||||
$eventDispatcher = static::getContainer()->get('event_dispatcher');
|
||||
$eventDispatcher->addListener(LeadEvents::LEAD_POST_SAVE, [$this, 'addPointsListener']);
|
||||
|
||||
/** @var LeadModel $model */
|
||||
$model = static::getContainer()->get('mautic.lead.model.lead');
|
||||
/** @var EntityManager $em */
|
||||
$em = static::getContainer()->get('doctrine.orm.entity_manager');
|
||||
|
||||
// Set company to trigger setPrimaryCompany()
|
||||
$lead = new Lead();
|
||||
$data = ['email' => 'pointtest@test.com', 'company' => 'PointTest'];
|
||||
$model->setFieldValues($lead, $data, false, true, true);
|
||||
|
||||
// Save to trigger points listener and setting primary company
|
||||
$model->saveEntity($lead);
|
||||
|
||||
// Clear from doctrine memory so we get a fresh entity to ensure the points are definitely saved
|
||||
$em->detach($lead);
|
||||
$lead = $model->getEntity($lead->getId());
|
||||
|
||||
$this->assertEquals(10, $lead->getPoints());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate a PointModel::triggerAction.
|
||||
*/
|
||||
public function addPointsListener(LeadEvent $event): void
|
||||
{
|
||||
// Prevent a loop
|
||||
if ($this->pointsAdded) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->pointsAdded = true;
|
||||
|
||||
$lead = $event->getLead();
|
||||
$lead->adjustPoints(10);
|
||||
|
||||
/** @var LeadModel $model */
|
||||
$model = static::getContainer()->get('mautic.lead.model.lead');
|
||||
$model->saveEntity($lead);
|
||||
}
|
||||
|
||||
public function testMultipleAssignedCompany(): void
|
||||
{
|
||||
self::assertEquals(2, count($this->getContactWithAssignTwoCompanies()));
|
||||
}
|
||||
|
||||
public function testSignleAssignedCompany(): void
|
||||
{
|
||||
$this->setUpSymfony(array_merge($this->configParams, ['contact_allow_multiple_companies' => 0]));
|
||||
|
||||
self::assertEquals(1, count($this->getContactWithAssignTwoCompanies()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int,array<int|string>>
|
||||
*
|
||||
* @throws DBALException
|
||||
* @throws \Doctrine\ORM\ORMException
|
||||
* @throws \Doctrine\ORM\OptimisticLockException
|
||||
*/
|
||||
protected function getContactWithAssignTwoCompanies(): array
|
||||
{
|
||||
$company = new Company();
|
||||
$company->setName('Doe Corp');
|
||||
|
||||
$this->em->persist($company);
|
||||
|
||||
$company2 = new Company();
|
||||
$company2->setName('Doe Corp 2');
|
||||
|
||||
$this->em->persist($company2);
|
||||
|
||||
$contact = new Lead();
|
||||
$contact->setEmail('test@test.com');
|
||||
|
||||
$this->em->persist($contact);
|
||||
$this->em->flush();
|
||||
|
||||
/** @var LeadModel $leadModel */
|
||||
$leadModel = $this->getContainer()->get('mautic.lead.model.lead');
|
||||
$leadModel->addToCompany($contact, $company);
|
||||
$leadModel->addToCompany($contact, $company2);
|
||||
|
||||
/** @var CompanyLeadRepository $companyLeadRepo */
|
||||
$companyLeadRepo = $this->em->getRepository(CompanyLead::class);
|
||||
$contactCompanies = $companyLeadRepo->getCompaniesByLeadId($contact->getId());
|
||||
|
||||
return $contactCompanies;
|
||||
}
|
||||
|
||||
public function testGetCustomLeadFieldLength(): void
|
||||
{
|
||||
$leadModel = $this->getContainer()->get('mautic.lead.model.lead');
|
||||
$fieldModel = $this->getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
// Create a lead field.
|
||||
$leadField = new LeadField();
|
||||
$leadField->setName('Test Field')
|
||||
->setAlias('custom_field_len_test')
|
||||
->setType('string')
|
||||
->setObject('lead')
|
||||
->setCharLengthLimit(150);
|
||||
$fieldModel->saveEntity($leadField);
|
||||
|
||||
// Create leads without adding value to the 'Test field'.
|
||||
$bob = new Lead();
|
||||
$bob->setFirstname('Bob')
|
||||
->setLastname('Smith')
|
||||
->setEmail('bob.smith@test.com');
|
||||
$leadModel->saveEntity($bob);
|
||||
|
||||
$jane = new Lead();
|
||||
$jane->setFirstname('Jane')
|
||||
->setLastname('Smith')
|
||||
->setEmail('jane.smith@test.com');
|
||||
$leadModel->saveEntity($jane);
|
||||
|
||||
$this->em->clear();
|
||||
|
||||
// Custom field is empty, and will return null.
|
||||
$length = $leadModel->getCustomLeadFieldLength([$leadField->getAlias()]);
|
||||
$this->assertNull($length[$leadField->getAlias()]);
|
||||
|
||||
// Update lead Bob with 'Test field' value.
|
||||
$hashStringBob = hash('sha256', __METHOD__);
|
||||
$bob->addUpdatedField($leadField->getAlias(), $hashStringBob);
|
||||
$leadModel->saveEntity($bob);
|
||||
|
||||
// Update lead Jane with 'Test field' value.
|
||||
$hashStringJane = hash('sha1', __METHOD__);
|
||||
$jane->addUpdatedField($leadField->getAlias(), $hashStringJane);
|
||||
$leadModel->saveEntity($jane);
|
||||
|
||||
$this->em->clear();
|
||||
|
||||
$length = $leadModel->getCustomLeadFieldLength([$leadField->getAlias()]);
|
||||
$this->assertEquals(strlen($hashStringBob), $length[$leadField->getAlias()]);
|
||||
}
|
||||
|
||||
public function testGettingUnknownCustomFieldLength(): void
|
||||
{
|
||||
$this->expectException(DBALException::class);
|
||||
|
||||
$leadModel = $this->getContainer()->get('mautic.lead.model.lead');
|
||||
$leadModel->getCustomLeadFieldLength(['unknown_field']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws MappingException
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('fieldValueProvider')]
|
||||
public function testSelectFieldSavesOnlyAllowedValuesInDB(string $selectFieldValue, ?string $expectedValue): void
|
||||
{
|
||||
$fieldModel = self::getContainer()->get('mautic.lead.model.field');
|
||||
|
||||
// Create a lead field.
|
||||
$selectField = new LeadField();
|
||||
$selectField->setName('Select Field')
|
||||
->setAlias('select_field')
|
||||
->setType('select')
|
||||
->setObject('lead')
|
||||
->setProperties(['list' => [
|
||||
['label' => 'Male', 'value' => 'male'],
|
||||
['label' => 'Female', 'value' => 'female'],
|
||||
['label' => 'Other\'s', 'value' => 'other\'s'],
|
||||
]]);
|
||||
$fieldModel->saveEntity($selectField);
|
||||
$this->em->clear();
|
||||
|
||||
$leadModel = self::getContainer()->get('mautic.lead.model.lead');
|
||||
|
||||
$fields = [
|
||||
'core' => [
|
||||
'First Name' => [
|
||||
'alias' => 'firstname',
|
||||
'type' => 'string',
|
||||
'value' => 'FirstName',
|
||||
],
|
||||
'Last Name' => [
|
||||
'alias' => 'lastname',
|
||||
'type' => 'string',
|
||||
'value' => 'LastName',
|
||||
],
|
||||
'Email' => [
|
||||
'alias' => 'email',
|
||||
'type' => 'email',
|
||||
'value' => 'firstname.lastname@test.com',
|
||||
],
|
||||
'Select Field' => [
|
||||
'alias' => $selectField->getAlias(),
|
||||
'type' => $selectField->getType(),
|
||||
'value' => $selectFieldValue,
|
||||
'properties' => ['list' => [
|
||||
['label' => 'Male', 'value' => 'male'],
|
||||
['label' => 'Female', 'value' => 'female'],
|
||||
// As it stores HTML encoded value.
|
||||
['label' => 'Other's', 'value' => 'other's'],
|
||||
]],
|
||||
],
|
||||
],
|
||||
];
|
||||
|
||||
// Create lead with multiple fields
|
||||
$lead = new Lead();
|
||||
$lead->setFields($fields);
|
||||
$lead->setFirstname('FirstName')
|
||||
->setLastname('LastName')
|
||||
->setEmail('firstname.lastname@test.com')
|
||||
->addUpdatedField($selectField->getAlias(), $selectFieldValue);
|
||||
$leadModel->saveEntity($lead);
|
||||
|
||||
$this->em->clear();
|
||||
|
||||
$lead = $leadModel->getEntity($lead->getId());
|
||||
|
||||
$this->assertSame($expectedValue, $lead->getFieldValue($selectField->getAlias()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public static function fieldValueProvider(): array
|
||||
{
|
||||
return [
|
||||
'allowed_value' => ['female', 'female'],
|
||||
'disallowed_value' => ['gibberish', null],
|
||||
'with_quotes' => ['other\'s', 'other\'s'],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,877 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\Tools\Pagination\Paginator;
|
||||
use Mautic\CategoryBundle\Model\CategoryModel;
|
||||
use Mautic\ChannelBundle\Helper\ChannelListHelper;
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\CoreBundle\Helper\PathsHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\EmailBundle\Helper\EmailValidator;
|
||||
use Mautic\LeadBundle\DataObject\LeadManipulator;
|
||||
use Mautic\LeadBundle\Entity\CompanyLeadRepository;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadEventLog;
|
||||
use Mautic\LeadBundle\Entity\LeadField;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\LeadBundle\Entity\StagesChangeLog;
|
||||
use Mautic\LeadBundle\Entity\StagesChangeLogRepository;
|
||||
use Mautic\LeadBundle\Event\LeadEvent;
|
||||
use Mautic\LeadBundle\Event\SaveBatchLeadsEvent;
|
||||
use Mautic\LeadBundle\Exception\ImportFailedException;
|
||||
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
|
||||
use Mautic\LeadBundle\LeadEvents;
|
||||
use Mautic\LeadBundle\Model\CompanyModel;
|
||||
use Mautic\LeadBundle\Model\FieldModel;
|
||||
use Mautic\LeadBundle\Model\IpAddressModel;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\LeadBundle\Model\ListModel;
|
||||
use Mautic\LeadBundle\Tests\Fixtures\Model\LeadModelStub;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Mautic\LeadBundle\Tracker\DeviceTracker;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\StageBundle\Entity\Stage;
|
||||
use Mautic\StageBundle\Entity\StageRepository;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use Mautic\UserBundle\Security\Provider\UserProvider;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\Form\FormFactory;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class LeadModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private MockObject|RequestStack $requestStack;
|
||||
|
||||
/**
|
||||
* @var MockObject|IpLookupHelper
|
||||
*/
|
||||
private MockObject $ipLookupHelperMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|PathsHelper
|
||||
*/
|
||||
private MockObject $pathsHelperMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|IntegrationHelper
|
||||
*/
|
||||
private MockObject $integrationHelperkMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|FieldModel
|
||||
*/
|
||||
private MockObject $fieldModelMock;
|
||||
|
||||
/**
|
||||
* @var MockObject&FieldsWithUniqueIdentifier
|
||||
*/
|
||||
private MockObject $fieldsWithUniqueIdentifier;
|
||||
|
||||
/**
|
||||
* @var MockObject|ListModel
|
||||
*/
|
||||
private MockObject $listModelMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|FormFactory
|
||||
*/
|
||||
private MockObject $formFactoryMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|CompanyModel
|
||||
*/
|
||||
private MockObject $companyModelMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|CategoryModel
|
||||
*/
|
||||
private MockObject $categoryModelMock;
|
||||
|
||||
private ChannelListHelper $channelListHelperMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|CoreParametersHelper
|
||||
*/
|
||||
private MockObject $coreParametersHelperMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|EmailValidator
|
||||
*/
|
||||
private MockObject $emailValidatorMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|UserProvider
|
||||
*/
|
||||
private MockObject $userProviderMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|ContactTracker
|
||||
*/
|
||||
private MockObject $contactTrackerMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|DeviceTracker
|
||||
*/
|
||||
private MockObject $deviceTrackerMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|IpAddressModel
|
||||
*/
|
||||
private MockObject $ipAddressModelMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|LeadRepository
|
||||
*/
|
||||
private MockObject $leadRepositoryMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|CompanyLeadRepository
|
||||
*/
|
||||
private MockObject $companyLeadRepositoryMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|UserHelper
|
||||
*/
|
||||
private MockObject $userHelperMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|EventDispatcherInterface
|
||||
*/
|
||||
private MockObject $dispatcherMock;
|
||||
|
||||
/**
|
||||
* @var MockObject|EntityManager
|
||||
*/
|
||||
private MockObject $entityManagerMock;
|
||||
|
||||
private LeadModel $leadModel;
|
||||
|
||||
/**
|
||||
* @var MockObject&Translator
|
||||
*/
|
||||
private MockObject $translator;
|
||||
|
||||
/**
|
||||
* @var MockObject&UrlGeneratorInterface
|
||||
*/
|
||||
private MockObject $urlGeneratorInterfaceMock;
|
||||
|
||||
/**
|
||||
* @var MockObject&LoggerInterface
|
||||
*/
|
||||
private MockObject $logger;
|
||||
|
||||
/**
|
||||
* @var MockObject&CorePermissions
|
||||
*/
|
||||
private MockObject $corePermissionsMock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->requestStack = new RequestStack();
|
||||
$this->requestStack->push(new Request());
|
||||
$this->ipLookupHelperMock = $this->createMock(IpLookupHelper::class);
|
||||
$this->pathsHelperMock = $this->createMock(PathsHelper::class);
|
||||
$this->integrationHelperkMock = $this->createMock(IntegrationHelper::class);
|
||||
$this->fieldModelMock = $this->createMock(FieldModel::class);
|
||||
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
|
||||
$this->listModelMock = $this->createMock(ListModel::class);
|
||||
$this->formFactoryMock = $this->createMock(FormFactory::class);
|
||||
$this->companyModelMock = $this->createMock(CompanyModel::class);
|
||||
$this->categoryModelMock = $this->createMock(CategoryModel::class);
|
||||
$this->channelListHelperMock = new ChannelListHelper($this->createMock(EventDispatcherInterface::class), $this->createMock(Translator::class));
|
||||
$this->coreParametersHelperMock = $this->createMock(CoreParametersHelper::class);
|
||||
$this->emailValidatorMock = $this->createMock(EmailValidator::class);
|
||||
$this->userProviderMock = $this->createMock(UserProvider::class);
|
||||
$this->contactTrackerMock = $this->createMock(ContactTracker::class);
|
||||
$this->deviceTrackerMock = $this->createMock(DeviceTracker::class);
|
||||
$this->ipAddressModelMock = $this->createMock(IpAddressModel::class);
|
||||
$this->leadRepositoryMock = $this->createMock(LeadRepository::class);
|
||||
$this->companyLeadRepositoryMock = $this->createMock(CompanyLeadRepository::class);
|
||||
$this->userHelperMock = $this->createMock(UserHelper::class);
|
||||
$this->dispatcherMock = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->entityManagerMock = $this->createMock(EntityManager::class);
|
||||
$this->corePermissionsMock = $this->createMock(CorePermissions::class);
|
||||
$this->translator = $this->createMock(Translator::class);
|
||||
$this->urlGeneratorInterfaceMock = $this->createMock(UrlGeneratorInterface::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->leadModel = new LeadModel(
|
||||
$this->requestStack,
|
||||
$this->ipLookupHelperMock,
|
||||
$this->pathsHelperMock,
|
||||
$this->integrationHelperkMock,
|
||||
$this->fieldModelMock,
|
||||
$this->fieldsWithUniqueIdentifier,
|
||||
$this->listModelMock,
|
||||
$this->formFactoryMock,
|
||||
$this->companyModelMock,
|
||||
$this->categoryModelMock,
|
||||
$this->channelListHelperMock,
|
||||
$this->coreParametersHelperMock,
|
||||
$this->emailValidatorMock,
|
||||
$this->userProviderMock,
|
||||
$this->contactTrackerMock,
|
||||
$this->deviceTrackerMock,
|
||||
$this->ipAddressModelMock,
|
||||
$this->entityManagerMock,
|
||||
$this->corePermissionsMock,
|
||||
$this->dispatcherMock,
|
||||
$this->urlGeneratorInterfaceMock,
|
||||
$this->translator,
|
||||
$this->userHelperMock,
|
||||
$this->logger,
|
||||
);
|
||||
|
||||
$this->setSecurity($this->leadModel);
|
||||
|
||||
$this->companyModelMock->method('getCompanyLeadRepository')->willReturn($this->companyLeadRepositoryMock);
|
||||
}
|
||||
|
||||
public function testIpLookupDoesNotAddCompanyIfConfiguredSo(): void
|
||||
{
|
||||
$this->mockGetLeadRepository();
|
||||
|
||||
$entity = new Lead();
|
||||
$ipAddress = new IpAddress('some.ip');
|
||||
|
||||
$ipAddress->setIpDetails(['organization' => 'Doctors Without Borders']);
|
||||
|
||||
$entity->addIpAddress($ipAddress);
|
||||
|
||||
$this->coreParametersHelperMock->method('get')
|
||||
->willReturnMap([
|
||||
['anonymize_ip', false, false],
|
||||
['ip_lookup_create_organization', false, false],
|
||||
]);
|
||||
|
||||
$this->setupFieldModelForIpLookupTest();
|
||||
$this->companyLeadRepositoryMock->expects($this->never())->method('getEntitiesByLead');
|
||||
$this->companyModelMock->expects($this->never())->method('getEntities');
|
||||
|
||||
$this->leadModel->saveEntity($entity);
|
||||
|
||||
$this->assertNull($entity->getCompany());
|
||||
$this->assertArrayNotHasKey('company', $entity->getUpdatedFields());
|
||||
}
|
||||
|
||||
public function testIpLookupAddsCompanyIfDoesNotExistInEntity(): void
|
||||
{
|
||||
$this->mockGetLeadRepository();
|
||||
|
||||
$companyFromIpLookup = 'Doctors Without Borders';
|
||||
$entity = new Lead();
|
||||
$ipAddress = new IpAddress('some.ip');
|
||||
|
||||
$ipAddress->setIpDetails(['organization' => $companyFromIpLookup]);
|
||||
|
||||
$entity->addIpAddress($ipAddress);
|
||||
|
||||
$this->coreParametersHelperMock->method('get')
|
||||
->willReturnMap([
|
||||
['anonymize_ip', false, false],
|
||||
['ip_lookup_create_organization', false, true],
|
||||
]);
|
||||
|
||||
$this->setupFieldModelForIpLookupTest();
|
||||
$this->companyLeadRepositoryMock->method('getEntitiesByLead')->willReturn([]);
|
||||
$this->companyModelMock->expects($this->any())
|
||||
->method('fetchCompanyFields')
|
||||
->willReturn([]);
|
||||
|
||||
$this->leadModel->saveEntity($entity);
|
||||
|
||||
$this->assertSame($companyFromIpLookup, $entity->getCompany());
|
||||
$this->assertSame($companyFromIpLookup, $entity->getUpdatedFields()['company']);
|
||||
}
|
||||
|
||||
public function testIpLookupAddsCompanyIfExistsInEntity(): void
|
||||
{
|
||||
$this->mockGetLeadRepository();
|
||||
|
||||
$companyFromIpLookup = 'Doctors Without Borders';
|
||||
$companyFromEntity = 'Red Cross';
|
||||
$entity = new Lead();
|
||||
$ipAddress = new IpAddress('some.ip');
|
||||
|
||||
$entity->setCompany($companyFromEntity);
|
||||
$ipAddress->setIpDetails(['organization' => $companyFromIpLookup]);
|
||||
|
||||
$entity->addIpAddress($ipAddress);
|
||||
|
||||
$this->coreParametersHelperMock->expects($this->once())->method('get')->with('anonymize_ip', false)->willReturn(false);
|
||||
$this->setupFieldModelForIpLookupTest();
|
||||
$this->companyLeadRepositoryMock->method('getEntitiesByLead')->willReturn([]);
|
||||
|
||||
$this->leadModel->saveEntity($entity);
|
||||
|
||||
$this->assertSame($companyFromEntity, $entity->getCompany());
|
||||
$this->assertFalse(isset($entity->getUpdatedFields()['company']));
|
||||
}
|
||||
|
||||
public function testCheckForDuplicateContact(): void
|
||||
{
|
||||
$this->fieldModelMock->expects($this->once())
|
||||
->method('getFieldList')
|
||||
->with(false, false, ['isPublished' => true, 'object' => 'lead'])
|
||||
->willReturn(['email' => 'Email', 'firstname' => 'First Name']);
|
||||
|
||||
$this->fieldsWithUniqueIdentifier->expects($this->once())
|
||||
->method('getFieldsWithUniqueIdentifier')
|
||||
->willReturn(['email' => 'Email']);
|
||||
|
||||
$this->fieldModelMock->expects($this->once())
|
||||
->method('getEntities')
|
||||
->willReturn($this->getFieldPaginatorFake());
|
||||
|
||||
$mockLeadModel = $this->createMockLeadModelForDuplicateTest();
|
||||
|
||||
$this->leadRepositoryMock->expects($this->once())
|
||||
->method('getLeadsByUniqueFields')
|
||||
->with(['email' => 'john@doe.com'], null)
|
||||
->willReturn([]);
|
||||
|
||||
// The availableLeadFields property should start empty.
|
||||
$this->assertEquals([], $mockLeadModel->getAvailableLeadFields());
|
||||
|
||||
$contact = $mockLeadModel->checkForDuplicateContact(['email' => 'john@doe.com', 'firstname' => 'John']);
|
||||
$this->assertEquals(['email' => 'Email', 'firstname' => 'First Name'], $mockLeadModel->getAvailableLeadFields());
|
||||
$this->assertEquals('john@doe.com', $contact->getEmail());
|
||||
$this->assertEquals('John', $contact->getFirstname());
|
||||
}
|
||||
|
||||
public function testCheckForDuplicateContactForOnlyPubliclyUpdatable(): void
|
||||
{
|
||||
$this->fieldModelMock->expects($this->once())
|
||||
->method('getFieldList')
|
||||
->with(false, false, ['isPublished' => true, 'object' => 'lead', 'isPubliclyUpdatable' => true])
|
||||
->willReturn(['email' => 'Email']);
|
||||
|
||||
$this->fieldsWithUniqueIdentifier->expects($this->once())
|
||||
->method('getFieldsWithUniqueIdentifier')
|
||||
->willReturn(['email' => 'Email']);
|
||||
|
||||
$this->fieldModelMock->expects($this->once())
|
||||
->method('getEntities')
|
||||
->willReturn($this->getFieldPaginatorFake());
|
||||
|
||||
/** @var LeadModel&MockObject $mockLeadModel */
|
||||
$mockLeadModel = $this->createMockLeadModelForDuplicateTest();
|
||||
|
||||
$this->leadRepositoryMock->expects($this->once())
|
||||
->method('getLeadsByUniqueFields')
|
||||
->with(['email' => 'john@doe.com'], null)
|
||||
->willReturn([]);
|
||||
|
||||
// The availableLeadFields property should start empty.
|
||||
$this->assertEquals([], $mockLeadModel->getAvailableLeadFields());
|
||||
|
||||
[$contact, $fields] = $mockLeadModel->checkForDuplicateContact(['email' => 'john@doe.com', 'firstname' => 'John'], true, true);
|
||||
$this->assertEquals(['email' => 'Email'], $mockLeadModel->getAvailableLeadFields());
|
||||
$this->assertEquals('john@doe.com', $contact->getEmail());
|
||||
$this->assertNull($contact->getFirstname());
|
||||
$this->assertEquals(['email' => 'john@doe.com'], $fields);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the Lead won't be set to the LeadEventLog if the Lead save fails.
|
||||
*/
|
||||
public function testImportWillNotSetLeadToLeadEventLogWhenLeadSaveFails(): void
|
||||
{
|
||||
$leadEventLog = new LeadEventLog();
|
||||
$mockLeadModel = $this->createMockLeadModelStub();
|
||||
$this->setupMockLeadModelForImport($mockLeadModel);
|
||||
|
||||
$mockLeadModel->expects($this->once())->method('saveEntity')->willThrowException(new \Exception());
|
||||
$mockLeadModel->expects($this->once())->method('checkForDuplicateContact')->willReturn(new Lead());
|
||||
|
||||
try {
|
||||
$mockLeadModel->import([], [], null, null, null, true, $leadEventLog);
|
||||
} catch (\Exception) {
|
||||
$this->assertNull($leadEventLog->getLead());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the Lead will be set to the LeadEventLog if the Lead save succeed.
|
||||
*/
|
||||
public function testImportWillSetLeadToLeadEventLogWhenLeadSaveSucceed(): void
|
||||
{
|
||||
$leadEventLog = new LeadEventLog();
|
||||
$lead = new Lead();
|
||||
|
||||
$mockLeadModel = $this->createMockLeadModelStub();
|
||||
$this->setupMockLeadModelForImport($mockLeadModel);
|
||||
|
||||
$mockLeadModel->expects($this->once())->method('checkForDuplicateContact')->willReturn($lead);
|
||||
|
||||
try {
|
||||
$mockLeadModel->import([], [], null, null, null, true, $leadEventLog);
|
||||
} catch (\Exception) {
|
||||
$this->assertEquals($lead, $leadEventLog->getLead());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that the tags will be added to the lead from the csv file.
|
||||
*/
|
||||
public function testImportWithTagsInCsvFile(): void
|
||||
{
|
||||
$mockLeadModel = $this->createMockLeadModelStub(['saveEntity', 'checkForDuplicateContact', 'modifyTags']);
|
||||
$this->setProperty($mockLeadModel, LeadModel::class, 'leadFieldModel', $this->fieldModelMock);
|
||||
$this->setupMockLeadModelForImport($mockLeadModel);
|
||||
|
||||
$mockLeadModel->expects($this->once())->method('checkForDuplicateContact')->willReturn(new Lead());
|
||||
$mockLeadModel->expects($this->once())->method('modifyTags')->willReturn(true);
|
||||
|
||||
$mockLeadModel->import(['tag' => 'tags'], ['tag' => 'Test 1|Test 2|Test 3']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test lead matching by ID.
|
||||
*/
|
||||
public function testImportMatchLeadById(): void
|
||||
{
|
||||
$leadEventLog = new LeadEventLog();
|
||||
$lead = new Lead();
|
||||
$lead->setId(21);
|
||||
|
||||
$mockLeadModel = $this->createMockLeadModelStub(['saveEntity', 'getEntity']);
|
||||
$this->setProperty($mockLeadModel, LeadModel::class, 'leadFieldModel', $this->fieldModelMock);
|
||||
$this->setupMockLeadModelForImport($mockLeadModel);
|
||||
|
||||
$mockLeadModel->expects($this->once())->method('getEntity')->willReturn($lead);
|
||||
|
||||
$merged = $mockLeadModel->import(['identifier' => 'id'], ['identifier' => '21'], null, null, null, true, $leadEventLog);
|
||||
$this->assertTrue($merged);
|
||||
}
|
||||
|
||||
public function testSetFieldValuesWithStage(): void
|
||||
{
|
||||
$lead = new Lead();
|
||||
$lead->setId(1);
|
||||
$lead->setFields(['all' => 'sth']);
|
||||
$stageMock = $this->createMock(Stage::class);
|
||||
$stageMock->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(1);
|
||||
$data = ['stage' => $stageMock];
|
||||
|
||||
$stagesChangeLogRepo = $this->createMock(StagesChangeLogRepository::class);
|
||||
$stagesChangeLogRepo->expects($this->once())
|
||||
->method('getCurrentLeadStage')
|
||||
->with($lead->getId())
|
||||
->willReturn(null);
|
||||
|
||||
$stageRepositoryMock = $this->createMock(StageRepository::class);
|
||||
$stageRepositoryMock->expects($this->once())
|
||||
->method('findByIdOrName')
|
||||
->with(1)
|
||||
->willReturn($stageMock);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->entityManagerMock->expects($matcher)
|
||||
->method('getRepository')->willReturnCallback(function (...$parameters) use ($matcher, $stagesChangeLogRepo, $stageRepositoryMock) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame(StagesChangeLog::class, $parameters[0]);
|
||||
|
||||
return $stagesChangeLogRepo;
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame(Stage::class, $parameters[0]);
|
||||
|
||||
return $stageRepositoryMock;
|
||||
}
|
||||
});
|
||||
|
||||
$this->translator->expects($this->once())
|
||||
->method('trans')
|
||||
->with('mautic.stage.event.changed');
|
||||
|
||||
$this->leadModel->setFieldValues($lead, $data, false, false);
|
||||
}
|
||||
|
||||
public function testImportIsIgnoringContactWithNotFoundStage(): void
|
||||
{
|
||||
$lead = new Lead();
|
||||
$lead->setId(1);
|
||||
$data = ['stage' => 'not found'];
|
||||
|
||||
$stagesChangeLogRepo = $this->createMock(StagesChangeLogRepository::class);
|
||||
$stagesChangeLogRepo->expects($this->once())
|
||||
->method('getCurrentLeadStage')
|
||||
->with($lead->getId())
|
||||
->willReturn(null);
|
||||
|
||||
$stageRepositoryMock = $this->createMock(StageRepository::class);
|
||||
$stageRepositoryMock->expects($this->once())
|
||||
->method('findByIdOrName')
|
||||
->with($data['stage'])
|
||||
->willReturn(null);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->entityManagerMock->expects($matcher)
|
||||
->method('getRepository')->willReturnCallback(function (...$parameters) use ($matcher, $stagesChangeLogRepo, $stageRepositoryMock) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame(StagesChangeLog::class, $parameters[0]);
|
||||
|
||||
return $stagesChangeLogRepo;
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame(Stage::class, $parameters[0]);
|
||||
|
||||
return $stageRepositoryMock;
|
||||
}
|
||||
});
|
||||
|
||||
$this->translator->expects($this->once())
|
||||
->method('trans')
|
||||
->with('mautic.lead.import.stage.not.exists', ['id' => $data['stage']])
|
||||
->willReturn('Stage not found');
|
||||
|
||||
$this->expectException(ImportFailedException::class);
|
||||
|
||||
$this->leadModel->setFieldValues($lead, $data, false, false);
|
||||
}
|
||||
|
||||
public function testManipulatorIsLoggedOnlyOnce(): void
|
||||
{
|
||||
$this->mockGetLeadRepository();
|
||||
|
||||
$contact = $this->createMock(Lead::class);
|
||||
$manipulator = new LeadManipulator('lead', 'import', 333);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getIpAddresses')
|
||||
->willReturn([]);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('isNewlyCreated')
|
||||
->willReturn(true);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getManipulator')
|
||||
->willReturn($manipulator);
|
||||
|
||||
$contact->expects($this->exactly(2))
|
||||
->method('getUpdatedFields')
|
||||
->willReturn([]);
|
||||
|
||||
$contact->expects($this->once())
|
||||
->method('addEventLog')
|
||||
->with($this->callback(function (LeadEventLog $leadEventLog) use ($contact) {
|
||||
$this->assertSame($contact, $leadEventLog->getLead());
|
||||
$this->assertSame('identified_contact', $leadEventLog->getAction());
|
||||
$this->assertSame('lead', $leadEventLog->getBundle());
|
||||
$this->assertSame('import', $leadEventLog->getObject());
|
||||
$this->assertSame(333, $leadEventLog->getObjectId());
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
$this->fieldModelMock->expects($this->exactly(2))
|
||||
->method('getFieldListWithProperties')
|
||||
->willReturn([]);
|
||||
|
||||
$this->fieldModelMock->expects($this->once())
|
||||
->method('getFieldList')
|
||||
->willReturn([]);
|
||||
|
||||
$this->leadModel->saveEntity($contact);
|
||||
$this->leadModel->saveEntity($contact);
|
||||
}
|
||||
|
||||
public function testImportHtmlFields(): void
|
||||
{
|
||||
$this->mockGetLeadRepository();
|
||||
|
||||
$fieldEntity = new LeadField();
|
||||
$fieldEntity->setAlias('custom_html_field');
|
||||
$fieldEntity->setLabel('Custom HTML Field');
|
||||
$fieldEntity->setType('html');
|
||||
$fieldEntity->setGroup('core');
|
||||
$fieldEntity->setObject('lead');
|
||||
|
||||
$fields = ['custom_html_field' => 'custom_html_field'];
|
||||
$data = ['custom_html_field' => '<html><head></head><body>Test</body></html>'];
|
||||
|
||||
$this->userHelperMock->method('getUser')
|
||||
->willReturn(new User());
|
||||
|
||||
$this->fieldModelMock->method('getFieldList')
|
||||
->willReturn([$fields]);
|
||||
|
||||
$this->fieldModelMock->expects($this->atLeastOnce())
|
||||
->method('getEntities')
|
||||
->willReturn($this->getFieldPaginatorFake());
|
||||
|
||||
$this->fieldModelMock->expects($this->once())
|
||||
->method('getEntityByAlias')
|
||||
->with('custom_html_field')
|
||||
->willReturn($fieldEntity);
|
||||
|
||||
$this->companyModelMock->expects($this->once())
|
||||
->method('extractCompanyDataFromImport')
|
||||
->willReturn([[], []]);
|
||||
|
||||
$this->leadModel->import($fields, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set protected property to an object.
|
||||
*
|
||||
* @param object $object
|
||||
* @param string $class
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
*/
|
||||
private function setProperty($object, $class, $property, $value): void
|
||||
{
|
||||
$reflectedProp = new \ReflectionProperty($class, $property);
|
||||
$reflectedProp->setAccessible(true);
|
||||
$reflectedProp->setValue($object, $value);
|
||||
}
|
||||
|
||||
private function mockGetLeadRepository(): void
|
||||
{
|
||||
$this->entityManagerMock->expects($this->any())
|
||||
->method('getRepository')
|
||||
->willReturnMap(
|
||||
[
|
||||
[Lead::class, $this->leadRepositoryMock],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
public function testModifiedCompanies(): void
|
||||
{
|
||||
$lead = $this->getLead(1);
|
||||
$companies = [];
|
||||
$leadCompanies = [];
|
||||
|
||||
for ($i = 1; $i <= 4; ++$i) {
|
||||
$companies[] = $i;
|
||||
}
|
||||
|
||||
// Imitate that companies with id 3 and 4 are already added to the lead
|
||||
for ($i = 3; $i <= 4; ++$i) {
|
||||
// Taking only company_id into consideration as only this is required in this case
|
||||
$leadCompanies[] = ['company_id' => $i];
|
||||
}
|
||||
|
||||
$this->companyModelMock->expects($this->once())
|
||||
->method('getCompanyLeadRepository')
|
||||
->willReturn($this->companyLeadRepositoryMock);
|
||||
|
||||
$this->companyLeadRepositoryMock->expects($this->once())
|
||||
->method('getCompaniesByLeadId')
|
||||
->with($lead->getId())
|
||||
->willReturn($leadCompanies);
|
||||
|
||||
$this->companyModelMock->expects($this->once())
|
||||
->method('addLeadToCompany')
|
||||
->with([$companies[0], $companies[1]], $lead);
|
||||
|
||||
$this->leadModel->modifyCompanies($lead, $companies);
|
||||
}
|
||||
|
||||
private function getLead(int $id): Lead
|
||||
{
|
||||
return new class($id) extends Lead {
|
||||
public function __construct(
|
||||
private int $id,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Paginator<mixed[]>
|
||||
*/
|
||||
private function getFieldPaginatorFake(): Paginator
|
||||
{
|
||||
return new class extends Paginator {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \ArrayIterator<int,array{label: string, alias: string, isPublished: bool, id: int, object: string, group: string, type: string}>
|
||||
*/
|
||||
public function getIterator()
|
||||
{
|
||||
return new \ArrayIterator([
|
||||
4 => ['label' => 'Email', 'alias' => 'email', 'isPublished' => true, 'id' => 4, 'object' => 'lead', 'group' => 'basic', 'type' => 'email'],
|
||||
5 => ['label' => 'First Name', 'alias' => 'firstname', 'isPublished' => true, 'id' => 5, 'object' => 'lead', 'group' => 'basic', 'type' => 'text'],
|
||||
]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function testDispatchBatchEvent(): void
|
||||
{
|
||||
$leadsParams = [];
|
||||
for ($x = 0; $x < 2; ++$x) {
|
||||
$lead = new Lead();
|
||||
$lead->setEmail(sprintf('test%s@test.cz', $x));
|
||||
$leadsParams[] = ['entity' => $lead, 'isNew'=> true, 'event'=> null];
|
||||
}
|
||||
$action = 'post_batch_save';
|
||||
|
||||
// Lead Model that provides access to dispatchBatchEvent
|
||||
$leadModel = new class($this->requestStack, $this->ipLookupHelperMock, $this->pathsHelperMock, $this->integrationHelperkMock, $this->fieldModelMock, $this->fieldsWithUniqueIdentifier, $this->listModelMock, $this->formFactoryMock, $this->companyModelMock, $this->categoryModelMock, $this->channelListHelperMock, $this->coreParametersHelperMock, $this->emailValidatorMock, $this->userProviderMock, $this->contactTrackerMock, $this->deviceTrackerMock, $this->ipAddressModelMock, $this->entityManagerMock, $this->corePermissionsMock, $this->dispatcherMock, $this->urlGeneratorInterfaceMock, $this->translator, $this->userHelperMock, $this->logger) extends LeadModel {
|
||||
/**
|
||||
* @param array<mixed> $leads
|
||||
*/
|
||||
public function dispatchBatchEventForTest(string $action, array $leads): ?\Symfony\Contracts\EventDispatcher\Event
|
||||
{
|
||||
return $this->dispatchBatchEvent($action, $leads);
|
||||
}
|
||||
};
|
||||
|
||||
$leadEvent1 = new LeadEvent($leadsParams[0]['entity'], $leadsParams[0]['isNew']);
|
||||
$leadEvent1->setEntityManager($this->entityManagerMock);
|
||||
|
||||
$leadEvent2 = new LeadEvent($leadsParams[1]['entity'], $leadsParams[1]['isNew']);
|
||||
$leadEvent2->setEntityManager($this->entityManagerMock);
|
||||
|
||||
$event = new SaveBatchLeadsEvent([
|
||||
$leadEvent1,
|
||||
$leadEvent2,
|
||||
]);
|
||||
|
||||
$this->dispatcherMock->expects($this->once())
|
||||
->method('hasListeners')
|
||||
->with(LeadEvents::LEAD_POST_BATCH_SAVE)
|
||||
->willReturn(true);
|
||||
$this->dispatcherMock->expects($this->once())
|
||||
->method('dispatch')
|
||||
->with($event, LeadEvents::LEAD_POST_BATCH_SAVE)
|
||||
->willReturn($event);
|
||||
|
||||
$leadModel->dispatchBatchEventForTest($action, $leadsParams);
|
||||
}
|
||||
|
||||
private function setSecurity(LeadModel $companyModel): void
|
||||
{
|
||||
$security = $this->createMock(CorePermissions::class);
|
||||
$security->method('hasEntityAccess')
|
||||
->willReturn(true);
|
||||
$security->method('isGranted')
|
||||
->willReturn(true);
|
||||
|
||||
$reflection = new \ReflectionClass($companyModel);
|
||||
$property = $reflection->getProperty('security');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($companyModel, $security);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures a mock UserHelper with a User.
|
||||
*/
|
||||
private function createMockUserHelper(): UserHelper&MockObject
|
||||
{
|
||||
/** @var UserHelper&MockObject $mockUserModel */
|
||||
$mockUserModel = $this->createMock(UserHelper::class);
|
||||
$mockUserModel->method('getUser')->willReturn(new User());
|
||||
|
||||
return $mockUserModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures a mock LeadModelStub with common setup.
|
||||
*
|
||||
* @param array<string> $methods
|
||||
*/
|
||||
private function createMockLeadModelStub(array $methods = ['saveEntity', 'checkForDuplicateContact']): MockObject
|
||||
{
|
||||
$mockLeadModel = $this->getMockBuilder(LeadModelStub::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods($methods)
|
||||
->getMock();
|
||||
|
||||
$mockLeadModel->setUserHelper($this->createMockUserHelper());
|
||||
$this->setSecurity($mockLeadModel);
|
||||
|
||||
return $mockLeadModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and configures a mock CompanyModel for import tests.
|
||||
*/
|
||||
private function createMockCompanyModelForImport(): MockObject
|
||||
{
|
||||
$mockCompanyModel = $this->getMockBuilder(CompanyModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['extractCompanyDataFromImport'])
|
||||
->getMock();
|
||||
|
||||
$mockCompanyModel->expects($this->once())
|
||||
->method('extractCompanyDataFromImport')
|
||||
->willReturn([[], []]);
|
||||
|
||||
return $mockCompanyModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up common properties for a mock LeadModel used in import tests.
|
||||
*/
|
||||
private function setupMockLeadModelForImport(MockObject $mockLeadModel): void
|
||||
{
|
||||
$mockCompanyModel = $this->createMockCompanyModelForImport();
|
||||
|
||||
$this->setProperty($mockLeadModel, LeadModel::class, 'companyModel', $mockCompanyModel);
|
||||
$this->setProperty($mockLeadModel, LeadModel::class, 'leadFields', [
|
||||
['alias' => 'email', 'type' => 'email', 'defaultValue' => ''],
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mock LeadModel for duplicate contact tests.
|
||||
*/
|
||||
private function createMockLeadModelForDuplicateTest(): MockObject
|
||||
{
|
||||
$mockLeadModel = $this->getMockBuilder(LeadModel::class)
|
||||
->disableOriginalConstructor()
|
||||
->onlyMethods(['getRepository'])
|
||||
->getMock();
|
||||
|
||||
$mockLeadModel->expects($this->once())
|
||||
->method('getRepository')
|
||||
->willReturn($this->leadRepositoryMock);
|
||||
|
||||
$this->setProperty($mockLeadModel, LeadModel::class, 'leadFieldModel', $this->fieldModelMock);
|
||||
$this->setProperty($mockLeadModel, LeadModel::class,
|
||||
'fieldsWithUniqueIdentifier', $this->fieldsWithUniqueIdentifier);
|
||||
|
||||
return $mockLeadModel;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up common field model mocks for IP lookup tests.
|
||||
*/
|
||||
private function setupFieldModelForIpLookupTest(): void
|
||||
{
|
||||
$this->fieldModelMock->method('getFieldListWithProperties')->willReturn([]);
|
||||
$this->fieldModelMock->method('getFieldList')->willReturn([]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadList;
|
||||
use Mautic\LeadBundle\Entity\LeadListRepository;
|
||||
use Mautic\LeadBundle\Entity\LeadRepository;
|
||||
use Mautic\LeadBundle\Model\ListModel;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
class ListModelFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testPublicSegmentsInContactPreferences(): void
|
||||
{
|
||||
$user = $this->em->getRepository(User::class)->findBy([], [], 1)[0];
|
||||
$firstLeadList = $this->createLeadList($user, 'First', true);
|
||||
$secondLeadList = $this->createLeadList($user, 'Second', false);
|
||||
$thirdLeadList = $this->createLeadList($user, 'Third', true);
|
||||
$this->em->flush();
|
||||
|
||||
/** @var LeadListRepository $repo */
|
||||
$repo = $this->em->getRepository(LeadList::class);
|
||||
$lists = $repo->getGlobalLists();
|
||||
|
||||
Assert::assertCount(2, $lists);
|
||||
Assert::assertArrayHasKey($firstLeadList->getId(), $lists);
|
||||
Assert::assertArrayHasKey($thirdLeadList->getId(), $lists);
|
||||
Assert::assertArrayNotHasKey(
|
||||
$secondLeadList->getId(),
|
||||
$lists,
|
||||
'Non-global lists should not be returned by the `getGlobalLists()` method.'
|
||||
);
|
||||
}
|
||||
|
||||
public function testSegmentLineChartData(): void
|
||||
{
|
||||
/** @var ListModel $segmentModel */
|
||||
$segmentModel = static::getContainer()->get('mautic.lead.model.list');
|
||||
|
||||
/** @var LeadRepository $contactRepository */
|
||||
$contactRepository = $this->em->getRepository(Lead::class);
|
||||
|
||||
$segment = new LeadList();
|
||||
$segment->setName('Segment A');
|
||||
|
||||
$segmentModel->saveEntity($segment);
|
||||
|
||||
$contacts = [new Lead(), new Lead(), new Lead(), new Lead()];
|
||||
|
||||
$contactRepository->saveEntities($contacts);
|
||||
|
||||
$segmentModel->addLead($contacts[0], $segment); // Emulating adding by a filter.
|
||||
$segmentModel->addLead($contacts[1], $segment); // Emulating adding by a filter.
|
||||
$segmentModel->addLead($contacts[2], $segment, true); // Manually added.
|
||||
$segmentModel->addLead($contacts[3], $segment, true); // Manually added.
|
||||
|
||||
$data = $segmentModel->getSegmentContactsLineChartData(
|
||||
'd',
|
||||
new \DateTime('1 month ago', new \DateTimeZone('UTC')),
|
||||
new \DateTime('now', new \DateTimeZone('UTC')),
|
||||
null,
|
||||
['leadlist_id' => ['value' => $segment->getId(), 'list_column_name' => 't.lead_id']]
|
||||
);
|
||||
|
||||
Assert::assertSame('added', strtolower($data['datasets'][0]['label']));
|
||||
Assert::assertSame('removed', strtolower($data['datasets'][1]['label']));
|
||||
Assert::assertSame('total', strtolower($data['datasets'][2]['label']));
|
||||
|
||||
Assert::assertSame(4, (int) end($data['datasets'][0]['data'])); // Added for today.
|
||||
Assert::assertSame(0, (int) end($data['datasets'][1]['data'])); // Removed for today.
|
||||
Assert::assertSame(4, (int) end($data['datasets'][2]['data'])); // Total for today.
|
||||
|
||||
// To make this interesting, lets' remove some contacts to see what happens.
|
||||
$segmentModel->removeLead($contacts[1], $segment); // Emulating removing by a filter.
|
||||
$segmentModel->removeLead($contacts[2], $segment, true); // Manually removed.
|
||||
|
||||
$data = $segmentModel->getSegmentContactsLineChartData(
|
||||
'd',
|
||||
new \DateTime('1 month ago', new \DateTimeZone('UTC')),
|
||||
new \DateTime('now', new \DateTimeZone('UTC')),
|
||||
null,
|
||||
['leadlist_id' => ['value' => $segment->getId(), 'list_column_name' => 't.lead_id']]
|
||||
);
|
||||
|
||||
Assert::assertSame(4, (int) end($data['datasets'][0]['data'])); // Added for today.
|
||||
Assert::assertSame(2, (int) end($data['datasets'][1]['data'])); // Removed for today.
|
||||
Assert::assertSame(2, (int) end($data['datasets'][2]['data'])); // Total for today.
|
||||
}
|
||||
|
||||
public function testSegmentLineChartDataWithoutFetchDataFromLeadListTable(): void
|
||||
{
|
||||
/** @var ListModel $segmentModel */
|
||||
$segmentModel = static::getContainer()->get('mautic.lead.model.list');
|
||||
|
||||
/** @var LeadRepository $contactRepository */
|
||||
$contactRepository = $this->em->getRepository(Lead::class);
|
||||
|
||||
$segment = new LeadList();
|
||||
$segment->setName('Segment A');
|
||||
|
||||
$segmentModel->saveEntity($segment);
|
||||
|
||||
$contacts = [new Lead()];
|
||||
|
||||
$contactRepository->saveEntities($contacts);
|
||||
|
||||
// Adding record in mautic_lead_lists_leads before 11 second from mautic_lead_event_log
|
||||
// using old code there should be double records means 2 but now it will show only 1 contact
|
||||
$segmentModel->addLead($contacts[0], $segment, true, false, 1, new \DateTime('-11 seconds', new \DateTimeZone('UTC'))); // Emulating adding by a filter.
|
||||
|
||||
$data = $segmentModel->getSegmentContactsLineChartData(
|
||||
'd',
|
||||
new \DateTime('-2 days', new \DateTimeZone('UTC')),
|
||||
new \DateTime('now', new \DateTimeZone('UTC')),
|
||||
null,
|
||||
['leadlist_id' => ['value' => $segment->getId(), 'list_column_name' => 't.lead_id']]
|
||||
);
|
||||
|
||||
// using old code there should be only 1 label added but now there should be all 3 labels
|
||||
Assert::assertSame('added', strtolower($data['datasets'][0]['label']));
|
||||
Assert::assertSame('removed', strtolower($data['datasets'][1]['label']));
|
||||
Assert::assertSame('total', strtolower($data['datasets'][2]['label']));
|
||||
|
||||
Assert::assertSame(1, (int) end($data['datasets'][0]['data'])); // Added for today.
|
||||
Assert::assertSame(0, (int) end($data['datasets'][1]['data'])); // Removed for today.
|
||||
Assert::assertSame(1, (int) end($data['datasets'][2]['data'])); // Total for today.
|
||||
|
||||
// To make this interesting, lets' remove some contacts to see what happens.
|
||||
$segmentModel->removeLead($contacts[0], $segment, true);
|
||||
|
||||
$data = $segmentModel->getSegmentContactsLineChartData(
|
||||
'd',
|
||||
new \DateTime('-2 days', new \DateTimeZone('UTC')),
|
||||
new \DateTime('now', new \DateTimeZone('UTC')),
|
||||
null,
|
||||
['leadlist_id' => ['value' => $segment->getId(), 'list_column_name' => 't.lead_id']]
|
||||
);
|
||||
|
||||
Assert::assertSame(1, (int) end($data['datasets'][0]['data'])); // Added for today.
|
||||
Assert::assertSame(1, (int) end($data['datasets'][1]['data'])); // Removed for today.
|
||||
Assert::assertSame(0, (int) end($data['datasets'][2]['data'])); // Total for today.
|
||||
}
|
||||
|
||||
private function createLeadList(User $user, string $name, bool $isGlobal): LeadList
|
||||
{
|
||||
$leadList = new LeadList();
|
||||
$leadList->setName($name);
|
||||
$leadList->setPublicName('Public'.$name);
|
||||
$leadList->setAlias(mb_strtolower($name));
|
||||
$leadList->setCreatedBy($user);
|
||||
$leadList->setIsGlobal($isGlobal);
|
||||
$this->em->persist($leadList);
|
||||
|
||||
return $leadList;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\CategoryBundle\Model\CategoryModel;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Entity\LeadList;
|
||||
use Mautic\LeadBundle\Entity\LeadListRepository;
|
||||
use Mautic\LeadBundle\Helper\SegmentCountCacheHelper;
|
||||
use Mautic\LeadBundle\Model\ListModel;
|
||||
use Mautic\LeadBundle\Segment\ContactSegmentService;
|
||||
use Mautic\LeadBundle\Segment\Stat\SegmentChartQueryFactory;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class ListModelTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var MockObject
|
||||
*/
|
||||
protected $fixture;
|
||||
|
||||
private ListModel $model;
|
||||
|
||||
/**
|
||||
* @var LeadListRepository|MockObject
|
||||
*/
|
||||
private MockObject $leadListRepositoryMock;
|
||||
|
||||
/**
|
||||
* @var SegmentCountCacheHelper|MockObject
|
||||
*/
|
||||
private MockObject $segmentCountCacheHelper;
|
||||
|
||||
/**
|
||||
* @var ContactSegmentService|MockObject
|
||||
*/
|
||||
private MockObject $contactSegmentServiceMock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$eventDispatcherInterfaceMock = $this->createMock(EventDispatcherInterface::class);
|
||||
$eventDispatcherInterfaceMock->method('dispatch');
|
||||
$loggerMock = $this->createMock(LoggerInterface::class);
|
||||
$translatorMock = $this->createMock(Translator::class);
|
||||
$this->leadListRepositoryMock = $this->createMock(LeadListRepository::class);
|
||||
|
||||
$entityManagerMock = $this->createMock(EntityManager::class);
|
||||
$entityManagerMock->method('getRepository')
|
||||
->willReturn($this->leadListRepositoryMock);
|
||||
|
||||
$coreParametersHelperMock = $this->createMock(CoreParametersHelper::class);
|
||||
$this->contactSegmentServiceMock = $this->createMock(ContactSegmentService::class);
|
||||
$segmentChartQueryFactoryMock = $this->createMock(SegmentChartQueryFactory::class);
|
||||
$this->segmentCountCacheHelper = $this->createMock(SegmentCountCacheHelper::class);
|
||||
$requestStackMock = $this->createMock(RequestStack::class);
|
||||
$categoryModelMock = $this->createMock(CategoryModel::class);
|
||||
$doNotContactRepositoryMock = $this->createMock(\Mautic\LeadBundle\Entity\DoNotContactRepository::class);
|
||||
|
||||
$this->model = new ListModel(
|
||||
$categoryModelMock,
|
||||
$coreParametersHelperMock,
|
||||
$this->contactSegmentServiceMock,
|
||||
$segmentChartQueryFactoryMock,
|
||||
$requestStackMock,
|
||||
$this->segmentCountCacheHelper,
|
||||
$doNotContactRepositoryMock,
|
||||
$entityManagerMock,
|
||||
$this->createMock(CorePermissions::class),
|
||||
$eventDispatcherInterfaceMock,
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$translatorMock,
|
||||
$this->createMock(UserHelper::class),
|
||||
$loggerMock
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|null $sourceType
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('sourceTypeTestDataProvider')]
|
||||
public function testGetSourceLists(array $getLookupResultsReturn, $sourceType, array $expected): void
|
||||
{
|
||||
$this->prepareMockForTestGetSourcesLists($getLookupResultsReturn);
|
||||
$result = $this->fixture->getSourceLists($sourceType);
|
||||
$this->assertEquals($expected, $result);
|
||||
}
|
||||
|
||||
private function prepareMockForTestGetSourcesLists(array $getLookupResultsReturn): void
|
||||
{
|
||||
$coreParametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
$leadSegment = $this->createMock(ContactSegmentService::class);
|
||||
$segmentChartQueryFactory = $this->createMock(SegmentChartQueryFactory::class);
|
||||
$requestStack = $this->createMock(RequestStack::class);
|
||||
$categoryModel = $this->createMock(CategoryModel::class);
|
||||
$categoryModel->expects($this->once())->method('getLookupResults')->willReturn($getLookupResultsReturn);
|
||||
$segmentCountCacheHelperMock = $this->createMock(SegmentCountCacheHelper::class);
|
||||
$doNotContactRepositoryMock = $this->createMock(\Mautic\LeadBundle\Entity\DoNotContactRepository::class);
|
||||
|
||||
$mockListModel = $this->getMockBuilder(ListModel::class)
|
||||
->setConstructorArgs([
|
||||
$categoryModel,
|
||||
$coreParametersHelper,
|
||||
$leadSegment,
|
||||
$segmentChartQueryFactory,
|
||||
$requestStack,
|
||||
$segmentCountCacheHelperMock,
|
||||
$doNotContactRepositoryMock,
|
||||
$this->createMock(EntityManagerInterface::class),
|
||||
$this->createMock(CorePermissions::class),
|
||||
$this->createMock(EventDispatcherInterface::class),
|
||||
$this->createMock(UrlGeneratorInterface::class),
|
||||
$this->createMock(Translator::class),
|
||||
$this->createMock(UserHelper::class),
|
||||
$this->createMock(LoggerInterface::class)])
|
||||
->onlyMethods([])
|
||||
->getMock();
|
||||
|
||||
$this->fixture = $mockListModel;
|
||||
}
|
||||
|
||||
public static function sourceTypeTestDataProvider(): array
|
||||
{
|
||||
return [
|
||||
[
|
||||
[],
|
||||
'categories',
|
||||
[],
|
||||
],
|
||||
[
|
||||
[
|
||||
0 => ['id' => 1, 'title' => 'Segment Test Category 1', 'bundle' => 'segment'],
|
||||
1 => ['id' => 2, 'title' => 'Segment Test Category 2', 'bundle' => 'segment'],
|
||||
],
|
||||
null,
|
||||
[
|
||||
'categories' => [
|
||||
1 => 'Segment Test Category 1',
|
||||
2 => 'Segment Test Category 2',
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
[
|
||||
0 => ['id' => 1, 'title' => 'Segment Test Category 1', 'bundle' => 'segment'],
|
||||
1 => ['id' => 2, 'title' => 'Segment Test Category 2', 'bundle' => 'segment'],
|
||||
],
|
||||
'categories',
|
||||
[
|
||||
1 => 'Segment Test Category 1',
|
||||
2 => 'Segment Test Category 2',
|
||||
],
|
||||
],
|
||||
[
|
||||
[],
|
||||
null,
|
||||
[
|
||||
'categories' => [],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testSegmentRebuildCountCacheGetsUpdated(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$leadCount = 433;
|
||||
|
||||
$this->leadListRepositoryMock
|
||||
->expects(self::once())
|
||||
->method('getLeadCount')
|
||||
->with($segmentId)
|
||||
->willReturn($leadCount);
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('setSegmentContactCount')
|
||||
->with($segmentId, $leadCount);
|
||||
|
||||
$newLeadsCount[$segmentId] = [
|
||||
'maxId' => 0,
|
||||
'count' => 0,
|
||||
];
|
||||
|
||||
$this->contactSegmentServiceMock
|
||||
->expects(self::once())
|
||||
->method('getNewLeadListLeadsCount')
|
||||
->with($leadList)
|
||||
->willReturn($newLeadsCount);
|
||||
|
||||
$orphanLeadsCount[$segmentId] = [
|
||||
'maxId' => 0,
|
||||
'count' => 0,
|
||||
];
|
||||
|
||||
$this->contactSegmentServiceMock
|
||||
->expects(self::once())
|
||||
->method('getOrphanedLeadListLeadsCount')
|
||||
->with($leadList)
|
||||
->willReturn($orphanLeadsCount);
|
||||
|
||||
self::assertSame(0, $this->model->rebuildListLeads($leadList));
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('getSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn($leadCount);
|
||||
|
||||
$leadCounts = $this->model->getSegmentContactCountFromCache([$segmentId]);
|
||||
|
||||
self::assertSame([$segmentId => $leadCount], $leadCounts);
|
||||
}
|
||||
|
||||
public function testRemoveLeadWillDecrementCacheCounter(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$lead = $this->mockLead(100);
|
||||
$currentLeadCount = 100;
|
||||
|
||||
$this->model->removeLead($lead, $leadList);
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('getSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn($currentLeadCount - 1);
|
||||
|
||||
$leadCounts = $this->model->getSegmentContactCountFromCache([$segmentId]);
|
||||
|
||||
self::assertSame([$segmentId => $currentLeadCount - 1], $leadCounts);
|
||||
}
|
||||
|
||||
public function testGetSegmentContactCountFromCache(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$leadCount = 100;
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('getSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn($leadCount);
|
||||
|
||||
$leadCounts = $this->model->getSegmentContactCountFromCache([$segmentId]);
|
||||
|
||||
self::assertSame([$segmentId => $leadCount], $leadCounts);
|
||||
}
|
||||
|
||||
public function testAddLeadWillIncrementCacheCounter(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$lead = $this->mockLead(100);
|
||||
$currentLeadCount = 100;
|
||||
|
||||
$this->model->addLead($lead, $leadList);
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('getSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn($currentLeadCount + 1);
|
||||
|
||||
$leadCounts = $this->model->getSegmentContactCountFromCache([$segmentId]);
|
||||
|
||||
self::assertSame([$segmentId => $currentLeadCount + 1], $leadCounts);
|
||||
}
|
||||
|
||||
public function testGetSegmentContactCountFromDatabaseHavingCache(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$leadCount = 100;
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('hasSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn(true);
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('getSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn($leadCount);
|
||||
|
||||
$leadCounts = $this->model->getSegmentContactCount([$segmentId]);
|
||||
|
||||
self::assertSame([$segmentId => $leadCount], $leadCounts);
|
||||
}
|
||||
|
||||
public function testGetSegmentContactCountFromDatabase(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$leadCount = 100;
|
||||
|
||||
$this->segmentCountCacheHelper
|
||||
->expects(self::once())
|
||||
->method('hasSegmentContactCount')
|
||||
->with($segmentId)
|
||||
->willReturn(false);
|
||||
|
||||
$this->leadListRepositoryMock
|
||||
->expects(self::once())
|
||||
->method('getLeadCount')
|
||||
->with($segmentId)
|
||||
->willReturn($leadCount);
|
||||
|
||||
$leadCounts = $this->model->getSegmentContactCount([$segmentId]);
|
||||
|
||||
self::assertSame([$segmentId => $leadCount], $leadCounts);
|
||||
}
|
||||
|
||||
public function testGetActiveSegmentContactCount(): void
|
||||
{
|
||||
$segmentId = 123;
|
||||
$total = 10;
|
||||
$dnc = 3;
|
||||
|
||||
$this->leadListRepositoryMock
|
||||
->expects(self::once())
|
||||
->method('getLeadCount')
|
||||
->with($segmentId)
|
||||
->willReturn($total);
|
||||
|
||||
$doNotContactRepository = $this->createMock(\Mautic\LeadBundle\Entity\DoNotContactRepository::class);
|
||||
$doNotContactRepository
|
||||
->expects(self::once())
|
||||
->method('getCount')
|
||||
->with(null, null, null, $segmentId)
|
||||
->willReturn($dnc);
|
||||
|
||||
$reflection = new \ReflectionClass($this->model);
|
||||
$property = $reflection->getProperty('doNotContactRepository');
|
||||
$property->setAccessible(true);
|
||||
$property->setValue($this->model, $doNotContactRepository);
|
||||
|
||||
$active = $this->model->getActiveSegmentContactCount($segmentId);
|
||||
self::assertSame($total - $dnc, $active);
|
||||
}
|
||||
|
||||
public function testLeadListExists(): void
|
||||
{
|
||||
$leadList = $this->mockLeadList(765);
|
||||
$segmentId = $leadList->getId();
|
||||
$this->leadListRepositoryMock->expects(self::once())
|
||||
->method('leadListExists')
|
||||
->with($segmentId)
|
||||
->willReturn(true);
|
||||
|
||||
self::assertTrue($this->model->leadListExists($segmentId));
|
||||
}
|
||||
|
||||
private function mockLeadList(int $id): LeadList
|
||||
{
|
||||
return new class($id) extends LeadList {
|
||||
public function __construct(
|
||||
private int $id,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private function mockLead(int $id): Lead
|
||||
{
|
||||
return new class($id) extends Lead {
|
||||
public function __construct(
|
||||
private int $id,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\LeadBundle\Model\SegmentActionModel;
|
||||
|
||||
class SegmentActionModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private \PHPUnit\Framework\MockObject\MockObject $contactMock5;
|
||||
|
||||
private \PHPUnit\Framework\MockObject\MockObject $contactMock6;
|
||||
|
||||
private \PHPUnit\Framework\MockObject\MockObject $contactModelMock;
|
||||
|
||||
private SegmentActionModel $actionModel;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->contactMock5 = $this->createMock(Lead::class);
|
||||
$this->contactMock6 = $this->createMock(Lead::class);
|
||||
$this->contactModelMock = $this->createMock(LeadModel::class);
|
||||
$this->actionModel = new SegmentActionModel($this->contactModelMock);
|
||||
}
|
||||
|
||||
public function testAddContactsToSegmentsEntityAccess(): void
|
||||
{
|
||||
$contacts = [5, 6];
|
||||
$segments = [4, 5];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5, $this->contactMock6]);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('canEditContact')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('addToLists')
|
||||
->with($this->contactMock6, $segments);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('saveEntities')
|
||||
->with([$this->contactMock5, $this->contactMock6]);
|
||||
|
||||
$this->actionModel->addContacts($contacts, $segments);
|
||||
}
|
||||
|
||||
public function testRemoveContactsFromSementsEntityAccess(): void
|
||||
{
|
||||
$contacts = [5, 6];
|
||||
$segments = [1, 2];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5, $this->contactMock6]);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('canEditContact')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('removeFromLists')
|
||||
->with($this->contactMock6, $segments);
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('saveEntities')
|
||||
->with([$this->contactMock5, $this->contactMock6]);
|
||||
|
||||
$this->actionModel->removeContacts($contacts, $segments);
|
||||
}
|
||||
|
||||
public function testAddContactsToSegments(): void
|
||||
{
|
||||
$contacts = [5, 6];
|
||||
$segments = [1, 2];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5, $this->contactMock6]);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('canEditContact')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('addToLists')->willReturnCallback(function (...$parameters) use ($matcher, $segments) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
$this->assertSame($segments, $parameters[1]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
$this->assertSame($segments, $parameters[1]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('saveEntities')
|
||||
->with([$this->contactMock5, $this->contactMock6]);
|
||||
|
||||
$this->actionModel->addContacts($contacts, $segments);
|
||||
}
|
||||
|
||||
public function testRemoveContactsFromCategories(): void
|
||||
{
|
||||
$contacts = [5, 6];
|
||||
$segments = [1, 2];
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('getLeadsByIds')
|
||||
->with($contacts)
|
||||
->willReturn([$this->contactMock5, $this->contactMock6]);
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('canEditContact')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
$matcher = $this->exactly(2);
|
||||
|
||||
$this->contactModelMock->expects($matcher)
|
||||
->method('removeFromLists')->willReturnCallback(function (...$parameters) use ($matcher, $segments) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock5, $parameters[0]);
|
||||
$this->assertSame($segments, $parameters[1]);
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertSame($this->contactMock6, $parameters[0]);
|
||||
}
|
||||
});
|
||||
|
||||
$this->contactModelMock->expects($this->once())
|
||||
->method('saveEntities')
|
||||
->with([$this->contactMock5, $this->contactMock6]);
|
||||
|
||||
$this->actionModel->removeContacts($contacts, $segments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\LeadBundle\Tests\Model;
|
||||
|
||||
use Mautic\CategoryBundle\Entity\Category;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\CoreBundle\Tests\Functional\CreateTestEntitiesTrait;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
|
||||
final class SetFrequencyRulesFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
use CreateTestEntitiesTrait;
|
||||
|
||||
public function testSetFrequencyRulesForCategorySubscriptionUnsubscription(): void
|
||||
{
|
||||
$categoriesFlags = [
|
||||
'one' => true,
|
||||
'two' => false,
|
||||
'three' => true,
|
||||
'four' => true,
|
||||
'five' => false,
|
||||
];
|
||||
|
||||
$categories = $this->createCategories($categoriesFlags);
|
||||
|
||||
$lead = $this->createLead('John', 'Doe', 'some@test.com');
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
// Subscribe categories.
|
||||
$categoriesToSubscribe = [];
|
||||
$categoriesToUnsubscribe = [];
|
||||
foreach ($categories as $category) {
|
||||
$categoriesToSubscribe[$category->getId()] = $category->getId();
|
||||
if (!$categoriesFlags[$category->getTitle()]) {
|
||||
$categoriesToUnsubscribe[$category->getId()] = $category->getId();
|
||||
}
|
||||
}
|
||||
|
||||
$data = [
|
||||
'global_categories' => array_keys($categoriesToSubscribe),
|
||||
'lead_lists' => [],
|
||||
];
|
||||
|
||||
/** @var LeadModel $model */
|
||||
$model = static::getContainer()->get('mautic.lead.model.lead');
|
||||
$model->setFrequencyRules($lead, $data, [], []);
|
||||
|
||||
$subscribedCategories = $model->getLeadCategories($lead);
|
||||
$this->assertEmpty(array_diff($subscribedCategories, array_keys($categoriesToSubscribe)));
|
||||
|
||||
// Unsubscribe categories.
|
||||
$data['global_categories'] = array_keys($categoriesToUnsubscribe);
|
||||
$model->setFrequencyRules($lead, $data, [], []);
|
||||
|
||||
$unsubscribedCategories = $model->getUnsubscribedLeadCategoriesIds($lead);
|
||||
$this->assertEmpty(array_diff($unsubscribedCategories, array_keys($categoriesToSubscribe)));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $cats
|
||||
*
|
||||
* @return Category[]
|
||||
*/
|
||||
private function createCategories(array $cats): array
|
||||
{
|
||||
$categories = [];
|
||||
foreach ($cats as $suffix => $flag) {
|
||||
$categories[$suffix] = $this->createCategory($suffix, $suffix);
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
return $categories;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user