Initial commit: CloudOps infrastructure platform

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

View File

@@ -0,0 +1,270 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
#[\PHPUnit\Framework\Attributes\CoversClass(ContactSegmentFilterCrate::class)]
class ContactSegmentFilterCrateTest extends \PHPUnit\Framework\TestCase
{
public function testEmptyFilter(): void
{
$filter = [];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertNull($contactSegmentFilterCrate->getGlue());
$this->assertNull($contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertNull($contactSegmentFilterCrate->getFilter());
$this->assertNull($contactSegmentFilterCrate->getOperator());
$this->assertFalse($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
public function testDateIdentifiedFilter(): void
{
$filter = [
'glue' => 'and',
'field' => 'date_identified',
'object' => 'lead',
'type' => 'datetime',
'filter' => null,
'display' => null,
'operator' => '!empty',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame('date_identified', $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertNull($contactSegmentFilterCrate->getFilter());
$this->assertSame('!empty', $contactSegmentFilterCrate->getOperator());
$this->assertFalse($contactSegmentFilterCrate->isBooleanType());
$this->assertTrue($contactSegmentFilterCrate->isDateType());
$this->assertTrue($contactSegmentFilterCrate->hasTimeParts());
}
public function testDateFilter(): void
{
$filter = [
'glue' => 'and',
'field' => 'date_identified',
'object' => 'lead',
'type' => 'date',
'filter' => null,
'display' => null,
'operator' => '!empty',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame('date_identified', $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertNull($contactSegmentFilterCrate->getFilter());
$this->assertSame('!empty', $contactSegmentFilterCrate->getOperator());
$this->assertFalse($contactSegmentFilterCrate->isBooleanType());
$this->assertTrue($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
public function testBooleanFilter(): void
{
$filter = [
'type' => 'boolean',
'filter' => '1',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertTrue($contactSegmentFilterCrate->getFilter());
$this->assertTrue($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
$this->assertTrue($contactSegmentFilterCrate->filterValueDoNotNeedAdjustment());
}
public function testNumericFilter(): void
{
$filter = [
'type' => 'number',
'filter' => '2',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame(2.0, $contactSegmentFilterCrate->getFilter());
$this->assertTrue($contactSegmentFilterCrate->isNumberType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
$this->assertTrue($contactSegmentFilterCrate->filterValueDoNotNeedAdjustment());
}
public function testCompanyTypeFilter(): void
{
$filter = [
'object' => 'company',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertFalse($contactSegmentFilterCrate->isContactType());
$this->assertTrue($contactSegmentFilterCrate->isCompanyType());
}
public function testMultiselectFilter(): void
{
$filter = [
'glue' => 'and',
'field' => 'multiselect_cf',
'object' => 'lead',
'type' => 'multiselect',
'filter' => [2, 4],
'display' => null,
'operator' => 'in',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame('multiselect_cf', $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertSame([2, 4], $contactSegmentFilterCrate->getFilter());
$this->assertSame('multiselect', $contactSegmentFilterCrate->getOperator());
$this->assertFalse($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
public function testNotMultiselectFilter(): void
{
$filter = [
'glue' => 'and',
'field' => 'multiselect_cf',
'object' => 'lead',
'type' => 'multiselect',
'filter' => [2, 4],
'display' => null,
'operator' => '!in',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame('multiselect_cf', $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertSame([2, 4], $contactSegmentFilterCrate->getFilter());
$this->assertSame('!multiselect', $contactSegmentFilterCrate->getOperator());
$this->assertFalse($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
public function testOldEqualInsteadOfInOperator(): void
{
$filter = [
'glue' => 'and',
'field' => 'tags',
'object' => 'lead',
'type' => 'tags',
'filter' => [3],
'display' => null,
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame('tags', $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertSame([3], $contactSegmentFilterCrate->getFilter());
$this->assertSame('in', $contactSegmentFilterCrate->getOperator());
$this->assertFalse($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
#[\PHPUnit\Framework\Attributes\DataProvider('specialFieldsToConvertToEmptyProvider')]
public function testSpecialFieldsToConvertToNotEmpty($field): void
{
$filter = [
'glue' => 'and',
'field' => $field,
'object' => 'lead',
'type' => 'boolean',
'filter' => 1,
'display' => null,
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame($field, $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertTrue($contactSegmentFilterCrate->getFilter());
$this->assertSame('notEmpty', $contactSegmentFilterCrate->getOperator());
$this->assertTrue($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
#[\PHPUnit\Framework\Attributes\DataProvider('specialFieldsToConvertToEmptyProvider')]
public function testSpecialFieldsToConvertToEmpty($field): void
{
$filter = [
'glue' => 'and',
'field' => $field,
'object' => 'lead',
'type' => 'boolean',
'filter' => 0,
'display' => null,
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertSame('and', $contactSegmentFilterCrate->getGlue());
$this->assertSame($field, $contactSegmentFilterCrate->getField());
$this->assertTrue($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertFalse($contactSegmentFilterCrate->getFilter());
$this->assertSame('empty', $contactSegmentFilterCrate->getOperator());
$this->assertTrue($contactSegmentFilterCrate->isBooleanType());
$this->assertFalse($contactSegmentFilterCrate->isDateType());
$this->assertFalse($contactSegmentFilterCrate->hasTimeParts());
}
public static function specialFieldsToConvertToEmptyProvider()
{
return [
['page_id'],
['email_id'],
['redirect_id'],
['notification'],
];
}
public function testBehaviorsTypeFilter(): void
{
$filter = [
'object' => 'behaviors',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$this->assertFalse($contactSegmentFilterCrate->isContactType());
$this->assertFalse($contactSegmentFilterCrate->isCompanyType());
$this->assertTrue($contactSegmentFilterCrate->isBehaviorsType());
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Segment\ContactSegmentFilterFactory;
use Mautic\LeadBundle\Segment\ContactSegmentFilters;
use Mautic\LeadBundle\Segment\Decorator\DecoratorFactory;
use Mautic\LeadBundle\Segment\Decorator\FilterDecoratorInterface;
use Mautic\LeadBundle\Segment\Query\Filter\FilterQueryBuilderInterface;
use Mautic\LeadBundle\Segment\TableSchemaColumnsCache;
use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
#[\PHPUnit\Framework\Attributes\CoversClass(ContactSegmentFilterFactory::class)]
class ContactSegmentFilterFactoryTest extends \PHPUnit\Framework\TestCase
{
public function testLeadFilter(): void
{
$tableSchemaColumnsCache = $this->createMock(TableSchemaColumnsCache::class);
$container = $this->createMock(Container::class);
$decoratorFactory = $this->createMock(DecoratorFactory::class);
$filterDecorator = $this->createMock(FilterDecoratorInterface::class);
$decoratorFactory->expects($this->exactly(6))
->method('getDecoratorForFilter')
->willReturn($filterDecorator);
$filterDecorator->expects($this->exactly(6))
->method('getQueryType')
->willReturn('MyQueryTypeId');
$filterQueryBuilder = $this->createMock(FilterQueryBuilderInterface::class);
$container->expects($this->exactly(6))
->method('get')
->with('MyQueryTypeId')
->willReturn($filterQueryBuilder);
$contactSegmentFilterFactory = new ContactSegmentFilterFactory($tableSchemaColumnsCache, $container, $decoratorFactory, $this->createMock(EventDispatcherInterface::class));
$leadList = new LeadList();
$leadList->setFilters([
[
'glue' => 'and',
'field' => 'date_identified',
'object' => 'lead',
'type' => 'datetime',
'filter' => null,
'display' => null,
'operator' => '!empty',
],
[
'glue' => 'and',
'type' => 'text',
'field' => 'hit_url',
'operator' => 'like',
'filter' => 'test.com',
'display' => '',
],
[
'glue' => 'or',
'type' => 'lookup',
'field' => 'state',
'operator' => '=',
'filter' => 'QLD',
'display' => '',
],
[
'glue' => 'or',
'type' => 'lookup',
'field' => 'state',
'operator' => ContactSegmentFilterFactory::CUSTOM_OPERATOR,
'properties' => [
[
'operator' => '=',
'filter' => 'QLD',
],
],
'merged_property' => [],
],
[
'glue' => 'and',
'field' => 'city',
'object' => 'lead',
'type' => 'text',
'filter' => null,
'display' => null,
'operator' => 'empty',
],
[
'glue' => 'and',
'field' => 'city',
'object' => 'lead',
'type' => 'text',
'filter' => null,
'display' => null,
'operator' => '!empty',
],
]);
$contactSegmentFilters = $contactSegmentFilterFactory->getSegmentFilters($leadList);
$this->assertInstanceOf(ContactSegmentFilters::class, $contactSegmentFilters);
$this->assertCount(6, $contactSegmentFilters);
}
}

View File

@@ -0,0 +1,471 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment;
use Mautic\LeadBundle\Segment\ContactSegmentFilter;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\BaseDecorator;
use Mautic\LeadBundle\Segment\Decorator\CompanyDecorator;
use Mautic\LeadBundle\Segment\Decorator\FilterDecoratorInterface;
use Mautic\LeadBundle\Segment\Exception\FieldNotFoundException;
use Mautic\LeadBundle\Segment\Query\Filter\FilterQueryBuilderInterface;
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
use Mautic\LeadBundle\Segment\TableSchemaColumnsCache;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ContactSegmentFilterTest extends TestCase
{
private ContactSegmentFilterCrate $contactSegmentFilterCrate;
/**
* @var FilterDecoratorInterface&MockObject
*/
private MockObject $filterDecorator;
/**
* @var TableSchemaColumnsCache|MockObject
*/
private MockObject $tableSchemaColumnCache;
/**
* @var FilterQueryBuilderInterface&MockObject
*/
private MockObject $filterQueryBuilder;
protected function setUp(): void
{
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$this->filterDecorator = $this->createMock(BaseDecorator::class);
$this->tableSchemaColumnCache = $this->createMock(TableSchemaColumnsCache::class);
$this->filterQueryBuilder = $this->createMock(FilterQueryBuilderInterface::class);
parent::setUp();
}
public function testGetType(): void
{
$type = 'type';
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate(['type' => $type]);
$filter = $this->createContactSegmentFilter();
self::assertEquals($type, $filter->getType());
}
public function testGetParameterValue(): void
{
$value = 'value';
$this->filterDecorator->expects(self::once())
->method('getParameterValue')
->with($this->contactSegmentFilterCrate)
->willReturn($value);
$filter = $this->createContactSegmentFilter();
self::assertEquals($value, $filter->getParameterValue());
}
public function testGetTable(): void
{
$table = 'table';
$this->filterDecorator->expects(self::once())
->method('getTable')
->with($this->contactSegmentFilterCrate)
->willReturn($table);
$filter = $this->createContactSegmentFilter();
self::assertEquals($table, $filter->getTable());
}
public function testIsColumnTypeBoolean(): void
{
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate(['type' => 'boolean']);
$filter = $this->createContactSegmentFilter();
self::assertTrue($filter->isColumnTypeBoolean());
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate(['type' => 'something']);
$filter = $this->createContactSegmentFilter();
self::assertFalse($filter->isColumnTypeBoolean());
}
public function testGetFilterQueryBuilder(): void
{
$filter = $this->createContactSegmentFilter();
$this->assertEquals($this->filterQueryBuilder, $filter->getFilterQueryBuilder());
}
public function testGetDoNotContactParts(): void
{
$filter = $this->createContactSegmentFilter();
$parts = $filter->getDoNotContactParts();
self::assertEquals('email', $parts->getChannel());
self::assertEquals(1, $parts->getParameterType());
}
public function testGetParameterHolder(): void
{
$argument = 'argument';
$expectedResult = 'expectedResult';
$this->filterDecorator->expects(self::once())
->method('getParameterHolder')
->with($this->contactSegmentFilterCrate, $argument)
->willReturn($expectedResult);
$filter = $this->createContactSegmentFilter();
self::assertEquals($expectedResult, $filter->getParameterHolder($argument));
}
public function testGetWhere(): void
{
$where = 'where';
$filter = $this->createContactSegmentFilter();
$this->filterDecorator->expects(self::once())
->method('getWhere')
->with($this->contactSegmentFilterCrate)
->willReturn($where);
self::assertEquals($where, $filter->getWhere());
}
public function testIsContactSegmentReference(): void
{
$filter = $this->createContactSegmentFilter();
$matcher = $this->exactly(2);
$this->filterDecorator->expects($matcher)->method('getField')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame($this->contactSegmentFilterCrate, $parameters[0]);
return 'leadlist';
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame($this->contactSegmentFilterCrate, $parameters[0]);
return 'something';
}
});
self::assertTrue($filter->isContactSegmentReference());
self::assertFalse($filter->isContactSegmentReference());
}
public function testGetGlue(): void
{
$glue = 'glue';
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate(['glue' => $glue]);
$filter = $this->createContactSegmentFilter();
self::assertSame($glue, $filter->getGlue());
}
public function testGetIntegrationCampaignParts(): void
{
$value = 'value';
$filter = $this->createContactSegmentFilter();
$this->filterDecorator->expects(self::once())
->method('getParameterValue')
->with($this->contactSegmentFilterCrate)
->willReturn($value);
$parts = $filter->getIntegrationCampaignParts();
self::assertEquals($value, $parts->getCampaignId());
}
public function testApplyQuery(): void
{
$queryBuilder = new QueryBuilder($this->createMock(\Doctrine\DBAL\Connection::class));
$this->filterQueryBuilder->expects(self::once())
->method('applyQuery')
->willReturn($queryBuilder);
$filter = $this->createContactSegmentFilter();
self::assertSame($queryBuilder, $filter->applyQuery($queryBuilder));
}
public function testGetRelationJoinTable(): void
{
$table = 'table';
$filter = $this->createContactSegmentFilter();
self::assertNull($filter->getRelationJoinTable());
$this->filterDecorator = $this->createMock(CompanyDecorator::class);
$this->filterDecorator->expects(self::once())
->method('getRelationJoinTable')
->willReturn($table);
$filter = $this->createContactSegmentFilter();
self::assertEquals($table, $filter->getRelationJoinTable());
}
public function testGetQueryType(): void
{
$type = 'type';
$filter = $this->createContactSegmentFilter();
$this->filterDecorator->expects(self::once())
->method('getQueryType')
->willReturn($type);
self::assertSame($type, $filter->getQueryType());
}
public function testGetNullValue(): void
{
$value = 'value';
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate(['null_value' => $value]);
$filter = $this->createContactSegmentFilter();
self::assertSame($value, $filter->getNullValue());
}
public function testGetColumnMissingColumn(): void
{
$dbName = 'dbName';
$tableName = 'tableName';
$columns = ['column1', 'column2'];
$this->tableSchemaColumnCache->expects(self::once())
->method('getCurrentDatabaseName')
->willReturn($dbName);
$this->filterDecorator->expects(self::exactly(2))
->method('getTable')
->with($this->contactSegmentFilterCrate)
->willReturn($tableName);
$this->tableSchemaColumnCache->expects(self::once())
->method('getColumns')
->with($tableName)
->willReturn($columns);
$this->filterDecorator->expects(self::exactly(2))
->method('getField')
->willReturn('notExistingColumn');
$this->expectException(FieldNotFoundException::class);
$filter = $this->createContactSegmentFilter();
$filter->getColumn();
}
public function testGetColumn(): void
{
$dbName = 'dbName';
$tableName = 'tableName';
$columns = ['column1' => 'something1', 'column2' => 'something2'];
$this->tableSchemaColumnCache->expects(self::once())
->method('getCurrentDatabaseName')
->willReturn($dbName);
$this->filterDecorator->expects(self::once())
->method('getTable')
->with($this->contactSegmentFilterCrate)
->willReturn($tableName);
$this->tableSchemaColumnCache->expects(self::once())
->method('getColumns')
->with($tableName)
->willReturn($columns);
$this->filterDecorator->expects(self::exactly(2))
->method('getField')
->willReturn('column1');
$filter = $this->createContactSegmentFilter();
$this->assertEquals('something1', $filter->getColumn());
}
public function testGetField(): void
{
$field = 'field';
$this->filterDecorator->expects(self::once())
->method('getField')
->willReturn($field);
$filter = $this->createContactSegmentFilter();
self::assertSame($field, $filter->getField());
}
public function testGetRelationJoinTableField(): void
{
$field = 'field';
$filter = $this->createContactSegmentFilter();
self::assertNull($filter->getRelationJoinTableField());
$this->filterDecorator = $this->createMock(CompanyDecorator::class);
$this->filterDecorator->expects(self::once())
->method('getRelationJoinTableField')
->willReturn($field);
$filter = $this->createContactSegmentFilter();
self::assertEquals($field, $filter->getRelationJoinTableField());
}
public function testGetAggregateFunction(): void
{
$function = 'function';
$filter = $this->createContactSegmentFilter();
$this->filterDecorator->expects(self::once())
->method('getAggregateFunc')
->with($this->contactSegmentFilterCrate)
->willReturn($function);
self::assertSame($function, $filter->getAggregateFunction());
}
public function testGetOperator(): void
{
$operator = 'operator';
$filter = $this->createContactSegmentFilter();
$this->filterDecorator->expects(self::once())
->method('getOperator')
->with($this->contactSegmentFilterCrate)
->willReturn($operator);
self::assertSame($operator, $filter->getOperator());
}
public function testToString(): void
{
$table = 'table';
$field = 'field';
$queryType = 'queryType';
$operator = 'operator';
$parameterValue = ['parameterValue'];
$expectedResult = sprintf(
'table: %s, %s on %s %s %s',
$table,
$field,
$queryType,
$operator,
json_encode($parameterValue)
);
$this->filterDecorator->expects(self::once())
->method('getTable')
->with($this->contactSegmentFilterCrate)
->willReturn($table);
$this->filterDecorator->expects(self::once())
->method('getField')
->with($this->contactSegmentFilterCrate)
->willReturn($field);
$this->filterDecorator->expects(self::once())
->method('getQueryType')
->willReturn($queryType);
$this->filterDecorator->expects(self::once())
->method('getOperator')
->with($this->contactSegmentFilterCrate)
->willReturn($operator);
$this->filterDecorator->expects(self::once())
->method('getParameterValue')
->with($this->contactSegmentFilterCrate)
->willReturn($parameterValue);
$filter = $this->createContactSegmentFilter();
$result = $filter->__toString();
self::assertEquals($expectedResult, $result);
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataDoesColumnSupportEmptyValue')]
public function testDoesColumnSupportEmptyValue(string $type, bool $doesColumnSupportEmptyValue): void
{
$this->contactSegmentFilterCrate = new ContactSegmentFilterCrate(['type' => $type]);
$filter = $this->createContactSegmentFilter();
self::assertEquals($doesColumnSupportEmptyValue, $filter->doesColumnSupportEmptyValue());
}
public function testBatchLimitersAreSetCorrectly(): void
{
$filter = new ContactSegmentFilter(
$this->contactSegmentFilterCrate,
$this->filterDecorator,
$this->tableSchemaColumnCache,
$this->filterQueryBuilder,
[
'minId' => 1,
'maxId' => 1,
]
);
self::assertSame([
'minId' => 1,
'maxId' => 1,
], $filter->getBatchLimiters());
}
/**
* @return iterable<array<bool|string>>
*/
public static function dataDoesColumnSupportEmptyValue(): iterable
{
yield ['boolean', true];
yield ['date', false];
yield ['datetime', false];
yield ['email', true];
yield ['html', true];
yield ['country', true];
yield ['locale', true];
yield ['lookup', true];
yield ['number', true];
yield ['tel', true];
yield ['region', true];
yield ['select', true];
yield ['multiselect', true];
yield ['text', true];
yield ['textarea', true];
yield ['time', true];
yield ['timezone', true];
yield ['url', true];
}
private function createContactSegmentFilter(): ContactSegmentFilter
{
return new ContactSegmentFilter(
$this->contactSegmentFilterCrate,
$this->filterDecorator,
$this->tableSchemaColumnCache,
$this->filterQueryBuilder
);
}
}

View File

@@ -0,0 +1,259 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment;
use Doctrine\Common\DataFixtures\ReferenceRepository;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\InstallBundle\InstallFixtures\ORM\LeadFieldData;
use Mautic\LeadBundle\Command\UpdateLeadListsCommand;
use Mautic\LeadBundle\DataFixtures\ORM\LoadCompanyData;
use Mautic\LeadBundle\DataFixtures\ORM\LoadLeadData;
use Mautic\LeadBundle\DataFixtures\ORM\LoadLeadListData;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Segment\ContactSegmentService;
use Mautic\LeadBundle\Segment\Exception\TableNotFoundException;
use Mautic\LeadBundle\Tests\DataFixtures\ORM\LoadClickData;
use Mautic\LeadBundle\Tests\DataFixtures\ORM\LoadDncData;
use Mautic\LeadBundle\Tests\DataFixtures\ORM\LoadPageHitData;
use Mautic\LeadBundle\Tests\DataFixtures\ORM\LoadSegmentsData;
use Mautic\LeadBundle\Tests\DataFixtures\ORM\LoadTagData;
use Mautic\PageBundle\DataFixtures\ORM\LoadPageCategoryData;
use Mautic\UserBundle\DataFixtures\ORM\LoadRoleData;
use Mautic\UserBundle\DataFixtures\ORM\LoadUserData;
use PHPUnit\Framework\Assert;
/**
* These tests cover same tests like \Mautic\LeadBundle\Tests\Model\ListModelFunctionalTest.
*/
class ContactSegmentServiceFunctionalTest extends MauticMysqlTestCase
{
/**
* @var ReferenceRepository
*/
private $fixtures;
/**
* @var ContactSegmentService
*/
private $contactSegmentService;
protected function setUp(): void
{
parent::setUp();
$this->fixtures = $this->loadFixtures(
[
LoadCompanyData::class,
LoadLeadListData::class,
LoadLeadData::class,
LeadFieldData::class,
LoadPageHitData::class,
LoadSegmentsData::class,
LoadPageCategoryData::class,
LoadRoleData::class,
LoadUserData::class,
LoadDncData::class,
LoadClickData::class,
LoadTagData::class,
],
false
)->getReferenceRepository();
$this->contactSegmentService = static::getContainer()->get('mautic.lead.model.lead_segment_service');
}
protected function beforeBeginTransaction(): void
{
$this->resetAutoincrement(
[
'leads',
'lead_lists',
]
);
}
public function testSegmentCountIsCorrect(): void
{
$this->testSymfonyCommand('mautic:segments:update', ['--env' => 'test']);
// purposively not using dataProvider here to avoid loading fixtures with each segment
foreach ($this->provideSegments() as $segmentAlias => $expectedCount) {
$reference = $this->getReference($segmentAlias);
$segmentContacts = $this->contactSegmentService->getTotalLeadListLeadsCount($reference);
Assert::assertEquals(
$expectedCount,
$segmentContacts[$reference->getId()]['count'],
sprintf('There should be %d in segment %s.', $expectedCount, $segmentAlias)
);
}
}
/**
* @return array<string,int>
*/
private function provideSegments(): array
{
return [
'segment-test-1' => 1,
'segment-test-2' => 4,
'segment-test-3' => 24,
'segment-test-4' => 1,
'segment-test-5' => 53,
'like-percent-end' => 32,
'segment-test-without-filters' => 0,
'segment-test-exclude-segment-with-filters' => 7,
'segment-test-include-segment-without-filters' => 0,
'segment-test-exclude-segment-without-filters' => 11,
'segment-test-include-segment-mixed-filters' => 24,
'segment-test-exclude-segment-mixed-filters' => 30,
'segment-test-mixed-include-exclude-filters' => 8,
'segment-test-manual-membership' => 12,
'segment-test-include-segment-manual-members' => 12,
'segment-test-exclude-segment-manual-members' => 25,
'segment-test-exclude-segment-without-other-filters' => 42,
'segment-test-include-segment-with-unrelated-segment-manual-removal' => 11,
'segment-membership-regexp' => 11,
'segment-company-only-fields' => 6,
'segment-including-segment-with-company-only-fields' => 14,
'name-is-not-equal-not-null-test' => 54,
'manually-unsubscribed-sms-test' => 1,
'clicked-link-in-any-email' => 2,
'did-not-click-link-in-any-email' => 52,
'clicked-link-in-any-email-on-specific-date' => 2,
'clicked-link-in-any-sms' => 3,
'clicked-link-in-any-sms-on-specific-date' => 2,
'tags-empty' => 52,
'tags-not-empty' => 2,
'segment-having-company' => 50,
'segment-not-having-company' => 4,
'has-email-and-visited-url' => 4,
];
}
public function testSegmentRebuildCommand(): void
{
// exclude the segment
$segmentTest3Ref = $this->getReference('segment-test-3');
$lastRebuiltDate = $segmentTest3Ref->getLastBuiltDate();
self::assertNull($lastRebuiltDate);
$this->testSymfonyCommand(
UpdateLeadListsCommand::NAME,
[
'--exclude' => [$segmentTest3Ref->getId()],
'--env' => 'test',
]
);
self::assertSame($lastRebuiltDate, $segmentTest3Ref->getLastBuiltDate(), 'Make sure the segment was not executed, if excluded.');
$this->testSymfonyCommand(
'mautic:segments:update',
[
'-i' => $segmentTest3Ref->getId(),
'--env' => 'test',
]
);
$segmentContacts = $this->contactSegmentService->getTotalLeadListLeadsCount($segmentTest3Ref);
$this->assertEquals(
24,
$segmentContacts[$segmentTest3Ref->getId()]['count'],
'There should be 24 contacts in the segment-test-3 segment after rebuilding from the command line.'
);
self::assertNotSame($lastRebuiltDate, $segmentTest3Ref->getLastBuiltDate(), 'Make sure the segment was executed, if not excluded.');
// Remove the title from all contacts, rebuild the list, and check that list is updated
$this->em->getConnection()->executeQuery(sprintf('UPDATE %sleads SET title = NULL;', MAUTIC_TABLE_PREFIX));
$this->testSymfonyCommand(
'mautic:segments:update',
[
'-i' => $segmentTest3Ref->getId(),
'--env' => 'test',
]
);
$segmentContacts = $this->contactSegmentService->getTotalLeadListLeadsCount($segmentTest3Ref);
$this->assertEquals(
0,
$segmentContacts[$segmentTest3Ref->getId()]['count'],
'There should be no contacts in the segment-test-3 segment after removing contact titles and rebuilding from the command line.'
);
$segmentTest40Ref = $this->getReference('segment-test-include-segment-with-or');
$this->testSymfonyCommand('mautic:segments:update', [
'-i' => $segmentTest40Ref->getId(),
'--env' => 'test',
]);
$segmentContacts = $this->contactSegmentService->getTotalLeadListLeadsCount($segmentTest40Ref);
$this->assertEquals(
11,
$segmentContacts[$segmentTest40Ref->getId()]['count'],
'There should be 11 contacts in the segment-test-include-segment-with-or segment after rebuilding from the command line.'
);
$segmentTest51Ref = $this->getReference('has-email-and-visited-url');
$this->testSymfonyCommand('mautic:segments:update', [
'-i' => $segmentTest51Ref->getId(),
'--env' => 'test',
]);
$segmentContacts = $this->contactSegmentService->getTotalLeadListLeadsCount($segmentTest51Ref);
$this->assertEquals(
4,
$segmentContacts[$segmentTest51Ref->getId()]['count'],
'There should be 4 contacts in the has-email-and-visited-url segment after rebuilding from the command line.'
);
// Change the url from page_hits with the right tracking_id, rebuild the list, and check that list is updated
$this->em->getConnection()->executeQuery(sprintf(
"UPDATE %spage_hits SET url = '%s' WHERE tracking_id = '%s';",
MAUTIC_TABLE_PREFIX,
'https://test/regex-segment-other.com',
'abcdr')
);
$this->testSymfonyCommand(
'mautic:segments:update',
[
'-i' => $segmentTest51Ref->getId(),
'--env' => 'test',
]
);
$segmentContacts = $this->contactSegmentService->getTotalLeadListLeadsCount($segmentTest51Ref);
$this->assertEquals(
0,
$segmentContacts[$segmentTest51Ref->getId()]['count'],
'There should be no contacts in the has-email-and-visited-url segment after removing contact titles and rebuilding from the command line.'
);
}
private function getReference(string $name): LeadList
{
/** @var LeadList $reference */
$reference = $this->fixtures->getReference($name);
return $reference;
}
public function testSegmentRebuildCommandFailsOnMissingTable(): void
{
/** @var ContactSegmentService $contactSegmentService */
$contactSegmentService = $this->getContainer()->get('mautic.lead.model.lead_segment_service');
$reference = $this->fixtures->getReference('table-name-missing-in-filter');
$this->expectException(TableNotFoundException::class);
$contactSegmentService->getTotalLeadListLeadsCount($reference);
}
}

View File

@@ -0,0 +1,367 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\ContactSegmentFilterOperator;
use Mautic\LeadBundle\Segment\Decorator\BaseDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(BaseDecorator::class)]
class BaseDecoratorTest extends \PHPUnit\Framework\TestCase
{
public function testGetField(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'glue' => 'and',
'field' => 'date_identified',
'object' => 'lead',
'type' => 'datetime',
'filter' => null,
'display' => null,
'operator' => '!empty',
]);
$this->assertSame('date_identified', $baseDecorator->getField($contactSegmentFilterCrate));
}
public function testGetTableLead(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'object' => 'lead',
]);
$this->assertSame(MAUTIC_TABLE_PREFIX.'leads', $baseDecorator->getTable($contactSegmentFilterCrate));
}
public function testGetTableCompany(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'object' => 'company',
]);
$this->assertSame(MAUTIC_TABLE_PREFIX.'companies', $baseDecorator->getTable($contactSegmentFilterCrate));
}
public function testGetOperatorEqual(): void
{
$contactSegmentFilterOperator = $this->createMock(ContactSegmentFilterOperator::class);
$contactSegmentFilterOperator->expects($this->once())
->method('fixOperator')
->with('=')
->willReturn('eq');
$baseDecorator = new BaseDecorator($contactSegmentFilterOperator);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'operator' => '=',
]);
$this->assertSame('eq', $baseDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorStartsWith(): void
{
$contactSegmentFilterOperator = $this->createMock(ContactSegmentFilterOperator::class);
$contactSegmentFilterOperator->expects($this->once())
->method('fixOperator')
->with('startsWith')
->willReturn('startsWith');
$baseDecorator = new BaseDecorator($contactSegmentFilterOperator);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'operator' => 'startsWith',
]);
$this->assertSame('like', $baseDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorEndsWith(): void
{
$contactSegmentFilterOperator = $this->createMock(ContactSegmentFilterOperator::class);
$contactSegmentFilterOperator->expects($this->once())
->method('fixOperator')
->with('endsWith')
->willReturn('endsWith');
$baseDecorator = new BaseDecorator($contactSegmentFilterOperator);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'operator' => 'endsWith',
]);
$this->assertSame('like', $baseDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorContainsWith(): void
{
$contactSegmentFilterOperator = $this->createMock(ContactSegmentFilterOperator::class);
$contactSegmentFilterOperator->expects($this->once())
->method('fixOperator')
->with('contains')
->willReturn('contains');
$baseDecorator = new BaseDecorator($contactSegmentFilterOperator);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'operator' => 'contains',
]);
$this->assertSame('like', $baseDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetQueryType(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$this->assertSame('mautic.lead.query.builder.basic', $baseDecorator->getQueryType($contactSegmentFilterCrate));
}
public function testGetParameterHolderSingle(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$this->assertSame(':argument', $baseDecorator->getParameterHolder($contactSegmentFilterCrate, 'argument'));
}
public function testGetParameterHolderArray(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$argument = [
'argument1',
'argument2',
'argument3',
];
$expected = [
':argument1',
':argument2',
':argument3',
];
$this->assertSame($expected, $baseDecorator->getParameterHolder($contactSegmentFilterCrate, $argument));
}
public function testGetParameterValueBoolean(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'boolean',
'filter' => '1',
]);
$this->assertTrue($baseDecorator->getParameterValue($contactSegmentFilterCrate));
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'boolean',
'filter' => '0',
]);
$this->assertFalse($baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueNumber(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'number',
'filter' => '1',
]);
$this->assertSame(1.0, $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueLikeNoPercent(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'like',
'filter' => 'Test string',
]);
$this->assertSame('%Test string%', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueNotLike(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => '!like',
'filter' => 'Test string',
]);
$this->assertSame('%Test string%', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueLikeWithOnePercent(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'like',
'filter' => '%Test string',
]);
$this->assertSame('%Test string', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueLikeWithTwoPercent(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'like',
'filter' => '%Test string%',
]);
$this->assertSame('%Test string%', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueStartsWith(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'startsWith',
'filter' => 'Test string',
]);
$this->assertSame('Test string%', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueEndsWith(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'endsWith',
'filter' => 'Test string',
]);
$this->assertSame('%Test string', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueContains(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'contains',
'filter' => 'Test string',
]);
$this->assertSame('%Test string%', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueContainsShouldNotBeEscaped(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'contains',
'filter' => 'Test with % and special characters \% should not be escaped %',
]);
$expected = '%Test with % and special characters \% should not be escaped %%';
$this->assertSame($expected, $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueRegex(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => 'regexp',
'filter' => 'Test \\\s string',
]);
$this->assertSame('Test \s string', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueNotRegex(): void
{
$baseDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'string',
'operator' => '!regexp',
'filter' => 'Test \\\s string',
]);
$this->assertSame('Test \s string', $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueMultiselect(): void
{
$baseDecorator = $this->getDecorator();
$expected = [
'(([|]|^)2([|]|$))',
'(([|]|^)4([|]|$))',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'multiselect',
'filter' => [2, 4],
'operator' => 'in',
]);
$this->assertSame($expected, $baseDecorator->getParameterValue($contactSegmentFilterCrate));
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'multiselect',
'filter' => [2, 4],
'operator' => '!in',
]);
$this->assertSame($expected, $baseDecorator->getParameterValue($contactSegmentFilterCrate));
$expected = [
'(([|]|^)Value \(1\)([|]|$))',
'(([|]|^)Value 2([|]|$))',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'type' => 'multiselect',
'filter' => ['Value (1)', 'Value 2'],
'operator' => 'in',
]);
$this->assertSame($expected, $baseDecorator->getParameterValue($contactSegmentFilterCrate));
}
/**
* @return BaseDecorator
*/
private function getDecorator()
{
$contactSegmentFilterOperator = $this->createMock(ContactSegmentFilterOperator::class);
return new BaseDecorator($contactSegmentFilterOperator);
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\ContactSegmentFilterOperator;
use Mautic\LeadBundle\Segment\Decorator\CustomMappedDecorator;
use Mautic\LeadBundle\Services\ContactSegmentFilterDictionary;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
#[\PHPUnit\Framework\Attributes\CoversClass(CustomMappedDecorator::class)]
class CustomMappedDecoratorTest extends \PHPUnit\Framework\TestCase
{
public function testGetField(): void
{
$customMappedDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'field' => 'lead_email_read_count',
]);
$this->assertSame('open_count', $customMappedDecorator->getField($contactSegmentFilterCrate));
}
public function testGetTable(): void
{
$customMappedDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'field' => 'lead_email_read_count',
]);
$this->assertSame(MAUTIC_TABLE_PREFIX.'email_stats', $customMappedDecorator->getTable($contactSegmentFilterCrate));
}
public function testGetQueryType(): void
{
$customMappedDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'field' => 'dnc_bounced',
]);
$this->assertSame('mautic.lead.query.builder.special.dnc', $customMappedDecorator->getQueryType($contactSegmentFilterCrate));
}
public function testGetForeignContactColumn(): void
{
$customMappedDecorator = $this->getDecorator();
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'field' => 'lead_email_read_count',
]);
$this->assertSame('lead_id', $customMappedDecorator->getForeignContactColumn($contactSegmentFilterCrate));
}
/**
* @return CustomMappedDecorator
*/
private function getDecorator()
{
$contactSegmentFilterOperator = $this->createMock(ContactSegmentFilterOperator::class);
$dispatcherMock = $this->createMock(EventDispatcherInterface::class);
$contactSegmentFilterDictionary = new ContactSegmentFilterDictionary($dispatcherMock);
return new CustomMappedDecorator($contactSegmentFilterOperator, $contactSegmentFilterDictionary);
}
}

View File

@@ -0,0 +1,282 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionFactory;
use Mautic\LeadBundle\Segment\Decorator\Date\Day\DateDayToday;
use Mautic\LeadBundle\Segment\Decorator\Date\Day\DateDayTomorrow;
use Mautic\LeadBundle\Segment\Decorator\Date\Day\DateDayYesterday;
use Mautic\LeadBundle\Segment\Decorator\Date\Month\DateMonthLast;
use Mautic\LeadBundle\Segment\Decorator\Date\Month\DateMonthNext;
use Mautic\LeadBundle\Segment\Decorator\Date\Month\DateMonthThis;
use Mautic\LeadBundle\Segment\Decorator\Date\Other\DateAnniversary;
use Mautic\LeadBundle\Segment\Decorator\Date\Other\DateDefault;
use Mautic\LeadBundle\Segment\Decorator\Date\Other\DateRelativeInterval;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekLast;
use Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekNext;
use Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekThis;
use Mautic\LeadBundle\Segment\Decorator\Date\Year\DateYearLast;
use Mautic\LeadBundle\Segment\Decorator\Date\Year\DateYearNext;
use Mautic\LeadBundle\Segment\Decorator\Date\Year\DateYearThis;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
use Mautic\LeadBundle\Segment\RelativeDate;
#[\PHPUnit\Framework\Attributes\CoversClass(DateOptionFactory::class)]
class DateOptionFactoryTest extends \PHPUnit\Framework\TestCase
{
public function testBirthday(): void
{
$filterName = 'birthday';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateAnniversary::class, $filterDecorator);
$filterName = 'anniversary';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateAnniversary::class, $filterDecorator);
}
public function testDayToday(): void
{
$filterName = 'today';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateDayToday::class, $filterDecorator);
}
public function testDayTomorrow(): void
{
$filterName = 'tomorrow';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateDayTomorrow::class, $filterDecorator);
}
public function testDayYesterday(): void
{
$filterName = 'yesterday';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateDayYesterday::class, $filterDecorator);
}
public function testWeekLast(): void
{
$filterName = 'last week';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateWeekLast::class, $filterDecorator);
}
public function testWeekNext(): void
{
$filterName = 'next week';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateWeekNext::class, $filterDecorator);
}
public function testWeekThis(): void
{
$filterName = 'this week';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateWeekThis::class, $filterDecorator);
}
public function testMonthLast(): void
{
$filterName = 'last month';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateMonthLast::class, $filterDecorator);
}
public function testMonthNext(): void
{
$filterName = 'next month';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateMonthNext::class, $filterDecorator);
}
public function testMonthThis(): void
{
$filterName = 'this month';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateMonthThis::class, $filterDecorator);
}
public function testYearLast(): void
{
$filterName = 'last year';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateYearLast::class, $filterDecorator);
}
public function testYearNext(): void
{
$filterName = 'next year';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateYearNext::class, $filterDecorator);
}
public function testYearThis(): void
{
$filterName = 'this year';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateYearThis::class, $filterDecorator);
}
public function testRelativePlus(): void
{
$filterName = '+20 days';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateRelativeInterval::class, $filterDecorator);
}
public function testRelativeMinus(): void
{
$filterName = '+20 days';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateRelativeInterval::class, $filterDecorator);
}
public function testRelativeAgo(): void
{
$filterName = '20 days ago';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateRelativeInterval::class, $filterDecorator);
}
public function testRelativeFirstDayOf(): void
{
$filterName = 'first day of previous month';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateRelativeInterval::class, $filterDecorator);
}
public function testRelativeLastDayOf(): void
{
$filterName = 'last day of previous month';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateRelativeInterval::class, $filterDecorator);
}
/**
* @return string[][]
*/
public static function getRelativeDateNotations(): array
{
return [
[DateRelativeInterval::class, 'first day of January 2021'],
[DateRelativeInterval::class, 'last day of January 2021'],
[DateRelativeInterval::class, '5 days ago'],
[DateDefault::class, 'day of January 2021'],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('getRelativeDateNotations')]
public function testRelativeDateNotations(string $expectedResult, string $filterName): void
{
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf($expectedResult, $filterDecorator);
}
public function testDateDefault(): void
{
$filterName = '2018-01-01';
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateDefault::class, $filterDecorator);
}
public function testNullValue(): void
{
$filterName = null;
$filterDecorator = $this->getFilterDecorator($filterName);
$this->assertInstanceOf(DateDefault::class, $filterDecorator);
}
/**
* @param string $filterName
*
* @return \Mautic\LeadBundle\Segment\Decorator\FilterDecoratorInterface
*/
private function getFilterDecorator($filterName)
{
$dateDecorator = $this->createMock(DateDecorator::class);
$relativeDate = $this->createMock(RelativeDate::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$relativeDate->method('getRelativeDateStrings')
->willReturn(
[
'mautic.lead.list.month_last' => 'last month',
'mautic.lead.list.month_next' => 'next month',
'mautic.lead.list.month_this' => 'this month',
'mautic.lead.list.today' => 'today',
'mautic.lead.list.tomorrow' => 'tomorrow',
'mautic.lead.list.yesterday' => 'yesterday',
'mautic.lead.list.week_last' => 'last week',
'mautic.lead.list.week_next' => 'next week',
'mautic.lead.list.week_this' => 'this week',
'mautic.lead.list.year_last' => 'last year',
'mautic.lead.list.year_next' => 'next year',
'mautic.lead.list.year_this' => 'this year',
'mautic.lead.list.birthday' => 'birthday',
'mautic.lead.list.anniversary' => 'anniversary',
]
);
$dateOptionFactory = new DateOptionFactory($dateDecorator, $relativeDate, $timezoneResolver);
$filter = [
'glue' => 'and',
'type' => 'datetime',
'object' => 'lead',
'field' => 'date_identified',
'operator' => '=',
'filter' => $filterName,
'display' => null,
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
return $dateOptionFactory->getDateOption($contactSegmentFilterCrate);
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Day;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Day\DateDayToday;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateDayToday::class)]
class DateDayTodayTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayToday($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayToday($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayToday($dateDecorator, $dateOptionParameters);
$this->assertEquals('2018-03-02%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataProviderForOperatorAndType')]
public function testGetParameterValueSingle(string $operator, string $type, string $expectedDateValue): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02 08:00:09', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => $operator,
'type' => $type,
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayToday($dateDecorator, $dateOptionParameters);
$this->assertEquals($expectedDateValue, $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
/**
* @return mixed[]
*/
public static function dataProviderForOperatorAndType(): iterable
{
yield ['lt', 'date', '2018-03-02'];
yield ['lte', 'date', '2018-03-02'];
yield ['gt', 'date', '2018-03-02'];
yield ['gte', 'date', '2018-03-02'];
yield ['lt', 'datetime', '2018-03-02 00:00:00'];
yield ['lte', 'datetime', '2018-03-02 23:59:59'];
yield ['gt', 'datetime', '2018-03-02 23:59:59'];
yield ['gte', 'datetime', '2018-03-02 00:00:00'];
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Day;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Day\DateDayTomorrow;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateDayTomorrow::class)]
class DateDayTomorrowTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayTomorrow($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayTomorrow($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayTomorrow($dateDecorator, $dateOptionParameters);
$this->assertEquals('2018-03-03%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataProviderForOperatorAndType')]
public function testGetParameterValueSingle(string $operator, string $type, string $expectedDateValue): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02 08:00:09', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => $operator,
'type' => $type,
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayTomorrow($dateDecorator, $dateOptionParameters);
$this->assertEquals($expectedDateValue, $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
/**
* @return mixed[]
*/
public static function dataProviderForOperatorAndType(): iterable
{
yield ['lt', 'date', '2018-03-03'];
yield ['lte', 'date', '2018-03-03'];
yield ['gt', 'date', '2018-03-03'];
yield ['gte', 'date', '2018-03-03'];
yield ['lt', 'datetime', '2018-03-03 00:00:00'];
yield ['lte', 'datetime', '2018-03-03 23:59:59'];
yield ['gt', 'datetime', '2018-03-03 23:59:59'];
yield ['gte', 'datetime', '2018-03-03 00:00:00'];
}
}

View File

@@ -0,0 +1,111 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Day;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Day\DateDayYesterday;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateDayYesterday::class)]
class DateDayYesterdayTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayYesterday($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayYesterday($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayYesterday($dateDecorator, $dateOptionParameters);
$this->assertEquals('2018-03-01%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataProviderForOperatorAndType')]
public function testGetParameterValueSingle(string $operator, string $type, string $expectedDateValue): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02 08:00:09', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => $operator,
'type' => $type,
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateDayYesterday($dateDecorator, $dateOptionParameters);
$this->assertEquals($expectedDateValue, $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
/**
* @return mixed[]
*/
public static function dataProviderForOperatorAndType(): iterable
{
yield ['lt', 'date', '2018-03-01'];
yield ['lte', 'date', '2018-03-01'];
yield ['gt', 'date', '2018-03-01'];
yield ['gte', 'date', '2018-03-01'];
yield ['lt', 'datetime', '2018-03-01 00:00:00'];
yield ['lte', 'datetime', '2018-03-01 23:59:59'];
yield ['gt', 'datetime', '2018-03-01 23:59:59'];
yield ['gte', 'datetime', '2018-03-01 00:00:00'];
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Month;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Month\DateMonthLast;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateMonthLast::class)]
class DateMonthLastTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthLast($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthLast($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of last month');
$this->assertEquals($expectedDate->format('Y-m-%'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of last month');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Month;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Month\DateMonthNext;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateMonthNext::class)]
class DateMonthNextTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthNext($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthNext($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of next month');
$this->assertEquals($expectedDate->format('Y-m-%'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of next month');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Month;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Month\DateMonthThis;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateMonthThis::class)]
class DateMonthThisTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthThis($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthThis($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of this month');
$this->assertEquals($expectedDate->format('Y-m-%'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateMonthThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of this month');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Other;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Other\DateAnniversary;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateAnniversary::class)]
class DateAnniversaryTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperator(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$filterDecorator = new DateAnniversary($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValue(): void
{
/**
* Today in '%-m-d%' format. This matches date and datetime fields.
*/
$expectedResult = '%'.(new \DateTime('now', new \DateTimeZone('UTC')))->format('-m-d').'%';
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$timezoneResolver->method('getDefaultDate')
->with(false)
->willReturn(
new DateTimeHelper(
new \DateTime('midnight today', new \DateTimeZone('UTC')), null, 'UTC')
);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$filterDecorator = new DateAnniversary($dateDecorator, $dateOptionParameters);
$this->assertEquals($expectedResult, $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueWithRelativeDate(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filter = [
'filter' => 'birthday +2days',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$filterDecorator = new DateAnniversary($dateDecorator, $dateOptionParameters);
$this->assertEquals('%-03-04%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetWhereReturnsCompositeExpression(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = ['field' => 'last_active'];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$dateDecorator->expects($this->once())
->method('getWhere')
->with($contactSegmentFilterCrate)
->willReturn(CompositeExpression::and('expr1', 'expr2'));
$filterDecorator = new DateAnniversary($dateDecorator, $dateOptionParameters);
$this->assertInstanceOf(
CompositeExpression::class,
$filterDecorator->getWhere($contactSegmentFilterCrate)
);
}
public function testGetWhereReturnsString(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = ['field' => 'last_active'];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
// Configure to return a string
$dateDecorator->expects($this->once())
->method('getWhere')
->willReturn('WHERE clause');
$filterDecorator = new DateAnniversary($dateDecorator, $dateOptionParameters);
$this->assertSame('WHERE clause', $filterDecorator->getWhere($contactSegmentFilterCrate));
}
public function testGetWhereReturnsNull(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = ['field' => 'last_active'];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
// Configure to return null
$dateDecorator->expects($this->once())
->method('getWhere')
->willReturn(null);
$filterDecorator = new DateAnniversary($dateDecorator, $dateOptionParameters);
$this->assertNull($filterDecorator->getWhere($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Other;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\Other\DateDefault;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateDefault::class)]
class DateDefaultTest extends \PHPUnit\Framework\TestCase
{
public function testGetParameterValue(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([]);
$filterDecorator = new DateDefault($dateDecorator, '2018-03-02 01:02:03');
$this->assertEquals('2018-03-02 01:02:03', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetWhereReturnsCompositeExpression(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$filterCrate = new ContactSegmentFilterCrate(['field' => 'last_active']);
// Configure DateDecorator mock to return CompositeExpression
$dateDecorator->expects($this->once())
->method('getWhere')
->with($filterCrate)
->willReturn(CompositeExpression::and('field = 1', 'field = 2'));
$filterDecorator = new DateDefault($dateDecorator, '2025-01-01');
$this->assertInstanceOf(
CompositeExpression::class,
$filterDecorator->getWhere($filterCrate)
);
}
public function testGetWhereReturnsString(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$filterCrate = new ContactSegmentFilterCrate(['field' => 'last_active']);
// Configure to return string
$dateDecorator->expects($this->once())
->method('getWhere')
->willReturn("date_field > '2025-01-01'");
$filterDecorator = new DateDefault($dateDecorator, '2025-01-01');
$this->assertSame(
"date_field > '2025-01-01'",
$filterDecorator->getWhere($filterCrate)
);
}
public function testGetWhereReturnsNull(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$filterCrate = new ContactSegmentFilterCrate(['field' => 'last_active']);
// Configure to return null
$dateDecorator->expects($this->once())
->method('getWhere')
->willReturn(null);
$filterDecorator = new DateDefault($dateDecorator, '2025-01-01');
$this->assertNull($filterDecorator->getWhere($filterCrate));
}
}

View File

@@ -0,0 +1,231 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Other;
use Doctrine\DBAL\Query\Expression\CompositeExpression;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\Other\DateRelativeInterval;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateRelativeInterval::class)]
class DateRelativeIntervalTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorNotEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$this->assertEquals('notLike', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('==<<'); // Test that value is really returned from Decorator
$filter = [
'operator' => '=<',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$this->assertEquals('==<<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValuePlusDaysWithGreaterOperator(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '>',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$this->assertEquals('2018-03-07', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueMinusMonthWithNotEqualOperator(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '-3 months', $dateOptionParameters);
$this->assertEquals('2017-12-02%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueDaysAgoWithNotEqualOperator(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '5 days ago', $dateOptionParameters);
$this->assertEquals('2018-02-25%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueYearsAgoWithGreaterOperator(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '>',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '2 years ago', $dateOptionParameters);
$this->assertEquals('2016-03-02', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueDaysWithEqualOperator(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('2018-03-02', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateRelativeInterval($dateDecorator, '5 days', $dateOptionParameters);
$this->assertEquals('2018-03-07%', $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetWhereReturnsCompositeExpression(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filterCrate = new ContactSegmentFilterCrate(['operator' => '=']);
$dateOptionParameters = new DateOptionParameters($filterCrate, [], $timezoneResolver);
// Mock CompositeExpression return
$composite = CompositeExpression::and('field = 1', 'field = 2');
$dateDecorator->expects($this->once())
->method('getWhere')
->with($filterCrate)
->willReturn($composite);
$decorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$result = $decorator->getWhere($filterCrate);
$this->assertInstanceOf(CompositeExpression::class, $result);
$this->assertSame($composite, $result);
}
public function testGetWhereReturnsString(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filterCrate = new ContactSegmentFilterCrate(['operator' => '=']);
$dateOptionParameters = new DateOptionParameters($filterCrate, [], $timezoneResolver);
// Mock string return
$expectedWhere = "date_field > '2023-01-01'";
$dateDecorator->expects($this->once())
->method('getWhere')
->willReturn($expectedWhere);
$decorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$this->assertSame($expectedWhere, $decorator->getWhere($filterCrate));
}
public function testGetWhereReturnsNull(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filterCrate = new ContactSegmentFilterCrate(['operator' => '=']);
$dateOptionParameters = new DateOptionParameters($filterCrate, [], $timezoneResolver);
// Mock null return
$dateDecorator->expects($this->once())
->method('getWhere')
->willReturn(null);
$decorator = new DateRelativeInterval($dateDecorator, '+5 days', $dateOptionParameters);
$this->assertNull($decorator->getWhere($filterCrate));
}
}

View File

@@ -0,0 +1,193 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date;
use Doctrine\Common\DataFixtures\ReferenceRepository;
use Mautic\CoreBundle\Helper\InputHelper;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\DataFixtures\ORM\LoadLeadData;
use Mautic\LeadBundle\DataFixtures\ORM\LoadLeadListData;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Entity\LeadRepository;
use Mautic\LeadBundle\Segment\ContactSegmentService;
use Mautic\LeadBundle\Tests\DataFixtures\ORM\LoadSegmentsData;
use Mautic\UserBundle\DataFixtures\ORM\LoadRoleData;
use Mautic\UserBundle\DataFixtures\ORM\LoadUserData;
class RelativeDateFunctionalTest extends MauticMysqlTestCase
{
/**
* @var ReferenceRepository
*/
private $fixtures;
protected function setUp(): void
{
parent::setUp();
$this->fixtures = $this->loadFixtures([
LoadLeadListData::class,
LoadLeadData::class,
LoadSegmentsData::class,
LoadRoleData::class,
LoadUserData::class,
], false)->getReferenceRepository();
}
protected function beforeBeginTransaction(): void
{
$this->resetAutoincrement([
'leads',
'lead_lists',
]);
}
public function testSegmentCountIsCorrectForToday(): void
{
$name = 'Today';
$lead = $this->createLead($name, 'midnight today', '+10 seconds');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForTomorrow(): void
{
$name = 'Tomorrow';
$lead = $this->createLead($name, 'midnight tomorrow', '+10 seconds');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForYesterday(): void
{
$name = 'Yesterday';
$lead = $this->createLead($name, 'midnight today', '-10 seconds');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForWeekLast(): void
{
$name = 'Last week';
$lead = $this->createLead($name, 'midnight monday last week', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForWeekNext(): void
{
$name = 'Next week';
$lead = $this->createLead($name, 'midnight monday next week', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForWeekThis(): void
{
$name = 'This week';
$lead = $this->createLead($name, 'midnight monday this week', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForMonthLast(): void
{
$name = 'Last month';
$lead = $this->createLead($name, 'midnight first day of last month', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForMonthNext(): void
{
$name = 'Next month';
$lead = $this->createLead($name, 'midnight first day of next month', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForMonthThis(): void
{
$name = 'This month';
$lead = $this->createLead($name, 'midnight first day of this month', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForYearLast(): void
{
$name = 'Last year';
$lead = $this->createLead($name, 'midnight first day of last year', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForYearNext(): void
{
$name = 'Next year';
$lead = $this->createLead($name, 'midnight first day of next year', '+2 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForRelativePlus(): void
{
$name = 'Relative plus';
$lead = $this->createLead($name, 'now', '+5 days');
$this->checkSegmentResult($name, $lead);
}
public function testSegmentCountIsCorrectForRelativeMinus(): void
{
$name = 'Relative minus';
$lead = $this->createLead($name, 'now', '-4 days');
$this->checkSegmentResult($name, $lead);
}
private function checkSegmentResult(string $name, Lead $lead): void
{
/** @var ContactSegmentService $contactSegmentService */
$contactSegmentService = static::getContainer()->get('mautic.lead.model.lead_segment_service');
$alias = strtolower(InputHelper::alphanum($name, false, '-'));
$segmentName = 'segment-with-relative-date-'.$alias;
/** @var LeadList $segmentRef */
$segmentRef = $this->fixtures->getReference($segmentName);
$segmentContacts = $contactSegmentService->getTotalLeadListLeadsCount($segmentRef);
$this->assertEquals(
1,
$segmentContacts[$segmentRef->getId()]['count'],
'There should be 1 contacts in the '.$segmentName.' segment.'
);
$this->assertEquals(
$lead->getId(),
$segmentContacts[$segmentRef->getId()]['maxId'],
'MaxId in the '.$segmentName.' segment should be ID of Lead.'
);
}
private function createLead(string $name, string $initialTime, string $dateModifier): Lead
{
/** @var LeadRepository $leadRepository */
$leadRepository = static::getContainer()->get('doctrine.orm.default_entity_manager')->getRepository(Lead::class);
$date = new \DateTime($initialTime, new \DateTimeZone('UTC'));
$date->modify($dateModifier);
$lead = new Lead();
$lead->setLastname('Date');
$lead->setFirstname($name);
$lead->setDateIdentified($date);
$leadRepository->saveEntity($lead);
return $lead;
}
}

View File

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
final class TimezoneResolverTest extends TestCase
{
#[\PHPUnit\Framework\Attributes\DataProvider('dataTimezones')]
public function testTimezones(?string $configuredTimezone, string $expectedTimezone): void
{
$coreParametersHelper = new class($configuredTimezone) extends CoreParametersHelper {
public function __construct(private ?string $configuredTimezone)
{
}
public function get($name, $default = null)
{
Assert::assertSame('default_timezone', $name);
return $this->configuredTimezone;
}
};
$timezoneResolver = new TimezoneResolver($coreParametersHelper);
Assert::assertSame(
$expectedTimezone,
$timezoneResolver->getDefaultDate(false)->getDateTime()->getTimezone()->getName()
);
}
/**
* @return iterable<string, array<?string>>
*/
public static function dataTimezones(): iterable
{
yield 'Default timezone' => [null, 'UTC'];
yield 'UTC timezone' => ['UTC', 'UTC'];
yield 'Prague timezone' => ['Europe/Prague', 'Europe/Prague'];
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Week;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekLast;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateWeekLast::class)]
class DateWeekLastTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekLast($dateDecorator, $dateOptionParameters);
$this->assertEquals('between', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekLast($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekLast($dateDecorator, $dateOptionParameters);
$expectedDateStart = new \DateTime('monday last week');
$expectedDateEnd = new \DateTime('sunday last week');
$this->assertEquals(
[
$expectedDateStart->format('Y-m-d'),
$expectedDateEnd->format('Y-m-d'),
],
$filterDecorator->getParameterValue($contactSegmentFilterCrate)
);
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('monday last week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueforGreaterOperatorIncludesSunday(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'gt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('sunday last week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueForLessThanOperatorIncludesSunday(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('sunday last week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Week;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekNext;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(\Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekLast::class)]
class DateWeekNextTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekNext($dateDecorator, $dateOptionParameters);
$this->assertEquals('between', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekNext($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekNext($dateDecorator, $dateOptionParameters);
$expectedDateStart = new \DateTime('monday next week');
$expectedDateEnd = new \DateTime('sunday next week');
$this->assertEquals(
[
$expectedDateStart->format('Y-m-d'),
$expectedDateEnd->format('Y-m-d'),
],
$filterDecorator->getParameterValue($contactSegmentFilterCrate)
);
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('monday next week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueforGreaterOperatorIncludesSunday(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'gt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('sunday next week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueForLessThanOperatorIncludesSunday(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('sunday next week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Week;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekThis;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(\Mautic\LeadBundle\Segment\Decorator\Date\Week\DateWeekLast::class)]
class DateWeekThisTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekThis($dateDecorator, $dateOptionParameters);
$this->assertEquals('between', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('=<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekThis($dateDecorator, $dateOptionParameters);
$this->assertEquals('=<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekThis($dateDecorator, $dateOptionParameters);
$expectedDateStart = new \DateTime('monday this week');
$expectedDateEnd = new \DateTime('sunday this week');
$this->assertEquals(
[
$expectedDateStart->format('Y-m-d'),
$expectedDateEnd->format('Y-m-d'),
],
$filterDecorator->getParameterValue($contactSegmentFilterCrate)
);
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('monday this week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueforGreaterOperatorIncludesSunday(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'gt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('sunday this week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueForLessThanOperatorIncludesSunday(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateWeekThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('sunday this week');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Year;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Year\DateYearLast;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateYearLast::class)]
class DateYearLastTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearLast($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('==<<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearLast($dateDecorator, $dateOptionParameters);
$this->assertEquals('==<<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of january last year');
$this->assertEquals($expectedDate->format('Y-%'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearLast($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of january last year');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Year;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Year\DateYearNext;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateYearNext::class)]
class DateYearNextTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearNext($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('==<<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearNext($dateDecorator, $dateOptionParameters);
$this->assertEquals('==<<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of january next year');
$this->assertEquals($expectedDate->format('Y-%'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearNext($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of january next year');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,98 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator\Date\Year;
use Mautic\CoreBundle\Helper\DateTimeHelper;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionParameters;
use Mautic\LeadBundle\Segment\Decorator\Date\TimezoneResolver;
use Mautic\LeadBundle\Segment\Decorator\Date\Year\DateYearThis;
use Mautic\LeadBundle\Segment\Decorator\DateDecorator;
#[\PHPUnit\Framework\Attributes\CoversClass(DateYearThis::class)]
class DateYearThisTest extends \PHPUnit\Framework\TestCase
{
public function testGetOperatorBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$filter = [
'operator' => '=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearThis($dateDecorator, $dateOptionParameters);
$this->assertEquals('like', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetOperatorLessOrEqual(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$dateDecorator->method('getOperator')
->with()
->willReturn('==<<');
$filter = [
'operator' => 'lte',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearThis($dateDecorator, $dateOptionParameters);
$this->assertEquals('==<<', $filterDecorator->getOperator($contactSegmentFilterCrate));
}
public function testGetParameterValueBetween(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => '!=',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of january this year');
$this->assertEquals($expectedDate->format('Y-%'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
public function testGetParameterValueSingle(): void
{
$dateDecorator = $this->createMock(DateDecorator::class);
$timezoneResolver = $this->createMock(TimezoneResolver::class);
$date = new DateTimeHelper('', null, 'local');
$timezoneResolver->method('getDefaultDate')
->with()
->willReturn($date);
$filter = [
'operator' => 'lt',
];
$contactSegmentFilterCrate = new ContactSegmentFilterCrate($filter);
$dateOptionParameters = new DateOptionParameters($contactSegmentFilterCrate, [], $timezoneResolver);
$filterDecorator = new DateYearThis($dateDecorator, $dateOptionParameters);
$expectedDate = new \DateTime('first day of january this year');
$this->assertEquals($expectedDate->format('Y-m-d'), $filterDecorator->getParameterValue($contactSegmentFilterCrate));
}
}

View File

@@ -0,0 +1,153 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\Decorator;
use Mautic\LeadBundle\Event\LeadListFiltersDecoratorDelegateEvent;
use Mautic\LeadBundle\LeadEvents;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\Decorator\BaseDecorator;
use Mautic\LeadBundle\Segment\Decorator\CompanyDecorator;
use Mautic\LeadBundle\Segment\Decorator\CustomMappedDecorator;
use Mautic\LeadBundle\Segment\Decorator\Date\DateOptionFactory;
use Mautic\LeadBundle\Segment\Decorator\DecoratorFactory;
use Mautic\LeadBundle\Segment\Decorator\FilterDecoratorInterface;
use Mautic\LeadBundle\Services\ContactSegmentFilterDictionary;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class DecoratorFactoryTest extends \PHPUnit\Framework\TestCase
{
/**
* @var MockObject|EventDispatcherInterface
*/
private MockObject $eventDispatcherMock;
private ContactSegmentFilterDictionary $contactSegmentFilterDictionary;
/**
* @var MockObject|BaseDecorator
*/
private MockObject $baseDecorator;
/**
* @var MockObject|CustomMappedDecorator
*/
private MockObject $customMappedDecorator;
/**
* @var MockObject|CompanyDecorator
*/
private MockObject $companyDecorator;
/**
* @var MockObject|DateOptionFactory
*/
private MockObject $dateOptionFactory;
private DecoratorFactory $decoratorFactory;
protected function setUp(): void
{
parent::setUp();
$this->eventDispatcherMock = $this->createMock(EventDispatcherInterface::class);
$this->contactSegmentFilterDictionary = new ContactSegmentFilterDictionary($this->eventDispatcherMock);
$this->baseDecorator = $this->createMock(BaseDecorator::class);
$this->customMappedDecorator = $this->createMock(CustomMappedDecorator::class);
$this->companyDecorator = $this->createMock(CompanyDecorator::class);
$this->dateOptionFactory = $this->createMock(DateOptionFactory::class);
$this->decoratorFactory = new DecoratorFactory(
$this->contactSegmentFilterDictionary,
$this->baseDecorator,
$this->customMappedDecorator,
$this->dateOptionFactory,
$this->companyDecorator,
$this->eventDispatcherMock);
}
public function testBaseDecorator(): void
{
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'field' => 'date_identified',
'type' => 'number',
]);
$this->assertInstanceOf(
BaseDecorator::class,
$this->decoratorFactory->getDecoratorForFilter($contactSegmentFilterCrate)
);
}
public function testCustomMappedDecorator(): void
{
$contactSegmentFilterCrate = new ContactSegmentFilterCrate([
'field' => 'hit_url_count',
'type' => 'number',
]);
$this->assertInstanceOf(
CustomMappedDecorator::class,
$this->decoratorFactory->getDecoratorForFilter($contactSegmentFilterCrate)
);
}
public function testDateDecoratorWhenNoSubscriberProvidesDecorator(): void
{
$filterDecoratorInterface = $this->createMock(FilterDecoratorInterface::class);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate(['type' => 'date']);
$this->dateOptionFactory->expects($this->once())
->method('getDateOption')
->with($contactSegmentFilterCrate)
->willReturn($filterDecoratorInterface);
$this->eventDispatcherMock->expects($this->once())
->method('dispatch')
->with(
$this->callback(
function (LeadListFiltersDecoratorDelegateEvent $event) use ($contactSegmentFilterCrate) {
$this->assertNull($event->getDecorator());
$this->assertSame($contactSegmentFilterCrate, $event->getCrate());
return true;
}
),
LeadEvents::SEGMENT_ON_DECORATOR_DELEGATE
);
$this->assertSame(
$filterDecoratorInterface,
$this->decoratorFactory->getDecoratorForFilter($contactSegmentFilterCrate)
);
}
public function testDateDecoratorWhenSubscriberProvidesDecorator(): void
{
$filterDecoratorInterface = $this->createMock(FilterDecoratorInterface::class);
$contactSegmentFilterCrate = new ContactSegmentFilterCrate(['type' => 'date']);
$this->dateOptionFactory->expects($this->never())
->method('getDateOption');
$this->eventDispatcherMock->expects($this->once())
->method('dispatch')
->with(
$this->callback(
function (LeadListFiltersDecoratorDelegateEvent $event) use ($contactSegmentFilterCrate, $filterDecoratorInterface) {
$this->assertNull($event->getDecorator());
$this->assertSame($contactSegmentFilterCrate, $event->getCrate());
$event->setDecorator($filterDecoratorInterface);
return true;
}
),
LeadEvents::SEGMENT_ON_DECORATOR_DELEGATE
);
$this->assertSame(
$filterDecoratorInterface,
$this->decoratorFactory->getDecoratorForFilter($contactSegmentFilterCrate)
);
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\DoNotContact;
use Mautic\LeadBundle\Entity\DoNotContact;
use Mautic\LeadBundle\Segment\DoNotContact\DoNotContactParts;
class DoNotContactPartsTest extends \PHPUnit\Framework\TestCase
{
#[\PHPUnit\Framework\Attributes\DataProvider('dataProvider')]
public function testParts(string $field, string $channel, int $type): void
{
$doNotContactParts = new DoNotContactParts($field);
$this->assertSame($channel, $doNotContactParts->getChannel());
$this->assertSame($type, $doNotContactParts->getParameterType());
}
/**
* @return iterable<array<string,string|int>>
*/
public static function dataProvider(): iterable
{
yield [
'field' => 'dnc_bounced',
'channel' => 'email',
'type' => DoNotContact::BOUNCED,
];
yield [
'field' => 'dnc_unsubscribed',
'channel' => 'email',
'type' => DoNotContact::UNSUBSCRIBED,
];
yield [
'field' => 'dnc_manual_email',
'channel' => 'email',
'type' => DoNotContact::MANUAL,
];
yield [
'field' => 'dnc_bounced_sms',
'channel' => 'sms',
'type' => DoNotContact::BOUNCED,
];
yield [
'field' => 'dnc_unsubscribed_sms',
'channel' => 'sms',
'type' => DoNotContact::UNSUBSCRIBED,
];
yield [
'field' => 'dnc_manual_sms',
'channel' => 'sms',
'type' => DoNotContact::MANUAL,
];
yield [
'field' => 'dnc_unsubscribed_sms_manually',
'channel' => 'sms',
'type' => DoNotContact::MANUAL,
];
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Mautic\LeadBundle\Tests\Segment\IntegrationCampaign;
use Mautic\LeadBundle\Segment\IntegrationCampaign\IntegrationCampaignParts;
#[\PHPUnit\Framework\Attributes\CoversClass(IntegrationCampaignParts::class)]
class IntegrationCampaignPartsTest extends \PHPUnit\Framework\TestCase
{
public function testConnectwise(): void
{
$field = 'Connectwise::283';
$doNotContactParts = new IntegrationCampaignParts($field);
$this->assertSame('Connectwise', $doNotContactParts->getIntegrationName());
$this->assertSame('283', $doNotContactParts->getCampaignId());
}
public function testSalesforceExplicit(): void
{
$field = 'Salesforce::22';
$doNotContactParts = new IntegrationCampaignParts($field);
$this->assertSame('Salesforce', $doNotContactParts->getIntegrationName());
$this->assertSame('22', $doNotContactParts->getCampaignId());
}
public function testSalesforceDefault(): void
{
$field = '44';
$doNotContactParts = new IntegrationCampaignParts($field);
$this->assertSame('Salesforce', $doNotContactParts->getIntegrationName());
$this->assertSame('44', $doNotContactParts->getCampaignId());
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Query;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Test\Doctrine\MockedConnectionTrait;
use Mautic\LeadBundle\Segment\Query\ContactSegmentQueryBuilder;
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
use Mautic\LeadBundle\Segment\RandomParameterName;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
class ContactSegmentQueryBuilderTest extends TestCase
{
use MockedConnectionTrait;
public function testAddNewContactsRestrictions(): void
{
$queryBuilder = new QueryBuilder($this->createConnection());
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$queryBuilder->where('NULL');
$filterQueryBuilder = new ContactSegmentQueryBuilder($this->createMock(EntityManager::class), new RandomParameterName(), new EventDispatcher());
Assert::assertSame($queryBuilder, $filterQueryBuilder->addNewContactsRestrictions($queryBuilder, 8));
Assert::assertSame('SELECT 1 FROM '.MAUTIC_TABLE_PREFIX.'leads l WHERE (NULL) AND (l.id NOT IN (SELECT par0.lead_id FROM '.MAUTIC_TABLE_PREFIX.'lead_lists_leads par0 WHERE par0.leadlist_id = 8))', $queryBuilder->getDebugOutput());
}
/**
* @return array<mixed>
*/
public static function dataAddNewContactsRestrictionsWithBatchLimiters(): iterable
{
yield [['minId' => 1, 'maxId' => 2], 'par0.lead_id BETWEEN 1 and 2'];
yield [['minId' => 1], 'par0.lead_id >= 1'];
yield [['maxId' => 2], 'par0.lead_id <= 2'];
yield [['lead_id' => 1], 'par0.lead_id = 1'];
}
/**
* @param array<string, mixed> $batchLimiters
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataAddNewContactsRestrictionsWithBatchLimiters')]
public function testAddNewContactsRestrictionsWithBatchLimiters(array $batchLimiters, string $expectedWhereClause): void
{
$queryBuilder = new QueryBuilder($this->createConnection());
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$queryBuilder->where('NULL');
$filterQueryBuilder = new ContactSegmentQueryBuilder($this->createMock(EntityManager::class), new RandomParameterName(), new EventDispatcher());
Assert::assertSame($queryBuilder, $filterQueryBuilder->addNewContactsRestrictions($queryBuilder, 8, $batchLimiters));
Assert::assertSame('SELECT 1 FROM '.MAUTIC_TABLE_PREFIX.'leads l WHERE (NULL) AND (l.id NOT IN (SELECT par0.lead_id FROM '.MAUTIC_TABLE_PREFIX.'lead_lists_leads par0 WHERE (par0.leadlist_id = 8) AND ('.$expectedWhereClause.')))', $queryBuilder->getDebugOutput());
}
private function createConnection(): Connection
{
return $this->getMockedConnection();
}
}

View File

@@ -0,0 +1,170 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Query\Filter;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Test\Doctrine\MockedConnectionTrait;
use Mautic\LeadBundle\Provider\FilterOperatorProviderInterface;
use Mautic\LeadBundle\Segment\ContactSegmentFilter;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\ContactSegmentFilterOperator;
use Mautic\LeadBundle\Segment\Decorator\BaseDecorator;
use Mautic\LeadBundle\Segment\Query\Filter\ChannelClickQueryBuilder;
use Mautic\LeadBundle\Segment\Query\Filter\FilterQueryBuilderInterface;
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
use Mautic\LeadBundle\Segment\RandomParameterName;
use Mautic\LeadBundle\Segment\TableSchemaColumnsCache;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ChannelClickQueryBuilderTest extends TestCase
{
use MockedConnectionTrait;
/**
* @var MockObject|RandomParameterName
*/
private MockObject $randomParameterMock;
/**
* @var MockObject|EventDispatcherInterface
*/
private MockObject $dispatcherMock;
/**
* @var Connection|MockObject
*/
private MockObject $connectionMock;
private ChannelClickQueryBuilder $queryBuilder;
public function setUp(): void
{
$this->randomParameterMock = $this->createMock(RandomParameterName::class);
$this->dispatcherMock = $this->createMock(EventDispatcherInterface::class);
$this->connectionMock = $this->getMockedConnection();
$this->queryBuilder = new ChannelClickQueryBuilder(
$this->randomParameterMock,
$this->dispatcherMock
);
$this->connectionMock->method('quote')
->willReturnArgument(0);
}
public function testGetServiceId(): void
{
$this->assertEquals(
'mautic.lead.query.builder.channel_click.value',
$this->queryBuilder::getServiceId()
);
}
/**
* @return array<mixed>
*/
public static function dataApplyQuery(): iterable
{
yield ['eq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email))'];
yield ['eq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email))'];
yield ['neq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email))'];
yield ['neq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email))'];
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQuery')]
public function testApplyQuery(string $operator, string $parameterValue, string $expectedQuery): void
{
$expectedQuery = str_replace('__PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
$queryBuilder = new QueryBuilder($this->connectionMock);
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->getContactSegmentFilter($operator, $parameterValue);
$this->randomParameterMock->method('generateRandomParameterName')
->willReturnOnConsecutiveCalls('queryAlias', 'para1', 'para2');
$this->queryBuilder->applyQuery($queryBuilder, $filter);
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @return array<mixed>
*/
public static function dataApplyQueryWithBatchLimitersMinMaxBoth(): iterable
{
yield [['minId' => 1, 'maxId' => 1], 'eq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id BETWEEN 1 and 1))'];
yield [['minId' => 1, 'maxId' => 1], 'eq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id BETWEEN 1 and 1))'];
yield [['minId' => 1, 'maxId' => 1], 'neq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id BETWEEN 1 and 1))'];
yield [['minId' => 1, 'maxId' => 1], 'neq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id BETWEEN 1 and 1))'];
yield [['minId' => 1], 'eq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id >= 1))'];
yield [['minId' => 1], 'eq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id >= 1))'];
yield [['minId' => 1], 'neq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id >= 1))'];
yield [['minId' => 1], 'neq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id >= 1))'];
yield [['maxId' => 1], 'eq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id <= 1))'];
yield [['maxId' => 1], 'eq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id <= 1))'];
yield [['maxId' => 1], 'neq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id <= 1))'];
yield [['maxId' => 1], 'neq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id <= 1))'];
yield [['lead_id' => 1], 'eq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id = 1))'];
yield [['lead_id' => 1], 'eq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id = 1))'];
yield [['lead_id' => 1], 'neq', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id = 1))'];
yield [['lead_id' => 1], 'neq', '0', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT para1.lead_id FROM __PREFIX__page_hits para1 WHERE (para1.redirect_id IS NOT NULL) AND (para1.lead_id IS NOT NULL) AND (para1.source = email) AND (para1.lead_id = 1))'];
}
/**
* @param array<string, mixed> $batchLimiters
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQueryWithBatchLimitersMinMaxBoth')]
public function testApplyQueryWithBatchLimitersMinMaxBoth(array $batchLimiters, string $operator, string $parameterValue, string $expectedQuery): void
{
$expectedQuery = str_replace('__PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
$queryBuilder = new QueryBuilder($this->connectionMock);
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->getContactSegmentFilter($operator, $parameterValue, $batchLimiters);
$this->randomParameterMock->method('generateRandomParameterName')
->willReturnOnConsecutiveCalls('queryAlias', 'para1', 'para2');
$this->queryBuilder->applyQuery($queryBuilder, $filter);
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @param array<string, mixed> $batchLimiters
*/
private function getContactSegmentFilter(string $operator, string $parameterValue, array $batchLimiters = []): ContactSegmentFilter
{
return new ContactSegmentFilter(
new ContactSegmentFilterCrate(
[
'operator' => $operator,
'glue' => 'and',
'field' => 'email_id',
'object' => 'behaviors',
'type' => 'boolean',
'properties' => [
'filter' => $parameterValue,
],
]
),
new BaseDecorator(new ContactSegmentFilterOperator(
$this->createMock(FilterOperatorProviderInterface::class)
)),
new TableSchemaColumnsCache($this->createMock(EntityManager::class)),
$this->createMock(FilterQueryBuilderInterface::class),
$batchLimiters
);
}
}

View File

@@ -0,0 +1,103 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Query\Filter;
use Doctrine\DBAL\Connection;
use Mautic\CoreBundle\Test\Doctrine\MockedConnectionTrait;
use Mautic\LeadBundle\Segment\ContactSegmentFilter;
use Mautic\LeadBundle\Segment\DoNotContact\DoNotContactParts;
use Mautic\LeadBundle\Segment\Query\Filter\DoNotContactFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
use Mautic\LeadBundle\Segment\RandomParameterName;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcher;
class DoNotContactFilterQueryBuilderTest extends TestCase
{
use MockedConnectionTrait;
public function testGetServiceId(): void
{
Assert::assertSame('mautic.lead.query.builder.special.dnc', DoNotContactFilterQueryBuilder::getServiceId());
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQuery')]
public function testApplyQuery(string $operator, string $parameterValue, string $expectedQuery): void
{
$queryBuilder = new QueryBuilder($this->createConnection());
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->createFilter($operator, $parameterValue);
$filterQueryBuilder = new DoNotContactFilterQueryBuilder(new RandomParameterName(), new EventDispatcher());
$expectedQuery = str_replace('__MAUTIC_TABLE_PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
Assert::assertSame($queryBuilder, $filterQueryBuilder->applyQuery($queryBuilder, $filter));
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @return iterable<array<string>>
*/
public static function dataApplyQuery(): iterable
{
yield ['eq', '1', 'SELECT 1 FROM __MAUTIC_TABLE_PREFIX__leads l WHERE l.id IN (SELECT par0.lead_id FROM __MAUTIC_TABLE_PREFIX__lead_donotcontact par0 WHERE (par0.reason = 1) AND (par0.channel = \'email\'))'];
yield ['eq', '0', 'SELECT 1 FROM __MAUTIC_TABLE_PREFIX__leads l WHERE l.id NOT IN (SELECT par0.lead_id FROM __MAUTIC_TABLE_PREFIX__lead_donotcontact par0 WHERE (par0.reason = 1) AND (par0.channel = \'email\'))'];
yield ['neq', '1', 'SELECT 1 FROM __MAUTIC_TABLE_PREFIX__leads l WHERE l.id NOT IN (SELECT par0.lead_id FROM __MAUTIC_TABLE_PREFIX__lead_donotcontact par0 WHERE (par0.reason = 1) AND (par0.channel = \'email\'))'];
yield ['neq', '0', 'SELECT 1 FROM __MAUTIC_TABLE_PREFIX__leads l WHERE l.id IN (SELECT par0.lead_id FROM __MAUTIC_TABLE_PREFIX__lead_donotcontact par0 WHERE (par0.reason = 1) AND (par0.channel = \'email\'))'];
}
private function createConnection(): Connection
{
return $this->getMockedConnection();
}
/**
* @param array<string, mixed> $batchLimiters
*/
private function createFilter(string $operator, string $parameterValue, array $batchLimiters = []): ContactSegmentFilter
{
return new class($operator, $parameterValue, $batchLimiters) extends ContactSegmentFilter {
/**
* @noinspection PhpMissingParentConstructorInspection
*/
public function __construct(
private string $operator,
private string $parameterValue,
/**
* @var array<string, mixed>
*/
private array $batchLimiters,
) {
}
public function getDoNotContactParts(): DoNotContactParts
{
return new DoNotContactParts('dnc_unsubscribed');
}
public function getOperator(): string
{
return $this->operator;
}
public function getParameterValue(): string
{
return $this->parameterValue;
}
public function getGlue(): string
{
return 'and';
}
public function getBatchLimiters(): array
{
return $this->batchLimiters;
}
};
}
}

View File

@@ -0,0 +1,276 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Query\Filter;
use Doctrine\DBAL\Connection;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Test\Doctrine\MockedConnectionTrait;
use Mautic\LeadBundle\Provider\FilterOperatorProvider;
use Mautic\LeadBundle\Segment\ContactSegmentFilter;
use Mautic\LeadBundle\Segment\ContactSegmentFilterCrate;
use Mautic\LeadBundle\Segment\ContactSegmentFilterOperator;
use Mautic\LeadBundle\Segment\Decorator\CustomMappedDecorator;
use Mautic\LeadBundle\Segment\Query\Filter\ForeignValueFilterQueryBuilder;
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
use Mautic\LeadBundle\Segment\RandomParameterName;
use Mautic\LeadBundle\Segment\TableSchemaColumnsCache;
use Mautic\LeadBundle\Services\ContactSegmentFilterDictionary;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class ForeignValueFilterQueryBuilderTest extends TestCase
{
use MockedConnectionTrait;
private RandomParameterName $randomParameter;
/**
* @var EventDispatcherInterface&MockObject
*/
private MockObject $dispatcher;
private ForeignValueFilterQueryBuilder $queryBuilder;
/**
* @var Connection&MockObject
*/
private MockObject $connectionMock;
public function setUp(): void
{
parent::setUp();
$this->randomParameter = new RandomParameterName();
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->connectionMock = $this->getMockedConnection();
$this->queryBuilder = new ForeignValueFilterQueryBuilder(
$this->randomParameter,
$this->dispatcher
);
}
public function testGetServiceId(): void
{
$this->assertEquals(
'mautic.lead.query.builder.foreign.value',
$this->queryBuilder::getServiceId()
);
}
/**
* @return array<mixed>
*/
public static function dataApplyQuery(): iterable
{
yield ['regexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url REGEXP '.com$')"];
yield ['notRegexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url NOT REGEXP '.com$')"];
yield ['eq', 'https://acquia.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url = 'https://acquia.com')"];
yield ['neq', 'https://acquia.com', "SELECT 1 FROM __PREFIX__leads l WHERE NOT EXISTS(SELECT NULL FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = l.id) AND ((par1.url = 'https://acquia.com') OR (par1.url IS NULL)))"];
yield ['empty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id NOT IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1)'];
yield ['notEmpty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1)'];
yield ['like', '%.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url LIKE '%.com')"];
yield ['notLike', '%.com', "SELECT 1 FROM __PREFIX__leads l WHERE NOT EXISTS(SELECT NULL FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = l.id) AND ((par1.url IS NULL) OR (par1.url LIKE '%.com')))"];
yield ['contains', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url LIKE '%.com%')"];
yield ['startsWith', 'https://', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url LIKE 'https://%')"];
yield ['endsWith', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url LIKE '%.com')"];
}
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQuery')]
public function testApplyQuery(string $operator, string $parameterValue, string $expectedQuery): void
{
$expectedQuery = str_replace('__PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
$queryBuilder = new QueryBuilder($this->connectionMock);
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->getContactSegmentFilter([
'object' => 'behaviors',
'glue' => 'and',
'field' => 'hit_url',
'type' => 'text',
'operator' => $operator,
'properties' => [
'filter' => $parameterValue,
],
'filter' => null,
'display' => null,
]);
$this->queryBuilder->applyQuery($queryBuilder, $filter);
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @return array<mixed>
*/
public static function dataApplyQueryAdditionalFilters(): iterable
{
yield ['in', [1, 2], 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par2.lead_id FROM __PREFIX__lead_categories par2 WHERE par2.category_id IN (1, 2))'];
yield ['notIn', [1, 2], 'SELECT 1 FROM __PREFIX__leads l WHERE NOT EXISTS(SELECT NULL FROM __PREFIX__lead_categories par2 WHERE (par2.lead_id = l.id) AND (par2.category_id IN (1, 2)))'];
}
/**
* @param array<string, mixed> $parameterValue
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQueryAdditionalFilters')]
public function testApplyQueryAdditionalFilters(string $operator, array $parameterValue, string $expectedQuery): void
{
$expectedQuery = str_replace('__PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
$queryBuilder = new QueryBuilder($this->connectionMock);
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->getContactSegmentFilter([
'glue' => 'and',
'field' => 'globalcategory',
'object' => 'lead',
'type' => 'globalcategory',
'operator' => $operator,
'properties' => [
'filter' => $parameterValue,
],
]);
$this->queryBuilder->applyQuery($queryBuilder, $filter);
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @return array<mixed>
*/
public static function dataApplyQueryWithBatchFilters(): iterable
{
yield [['minId' => 1, 'maxId' => 2], 'regexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url REGEXP '.com$'))"];
yield [['minId' => 1], 'regexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url REGEXP '.com$'))"];
yield [['maxId' => 2], 'regexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url REGEXP '.com$'))"];
yield [['lead_id' => 1], 'regexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url REGEXP '.com$'))"];
yield [['minId' => 1, 'maxId' => 2], 'notRegexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url NOT REGEXP '.com$'))"];
yield [['minId' => 1], 'notRegexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url NOT REGEXP '.com$'))"];
yield [['maxId' => 2], 'notRegexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url NOT REGEXP '.com$'))"];
yield [['lead_id' => 1], 'notRegexp', '.com$', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url NOT REGEXP '.com$'))"];
yield [['minId' => 1, 'maxId' => 2], 'eq', 'https://acquia.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url = 'https://acquia.com'))"];
yield [['minId' => 1], 'eq', 'https://acquia.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url = 'https://acquia.com'))"];
yield [['maxId' => 2], 'eq', 'https://acquia.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url = 'https://acquia.com'))"];
yield [['lead_id' => 1], 'eq', 'https://acquia.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url = 'https://acquia.com'))"]; // yield ['empty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.url IS NULL)'];
yield [['minId' => 1, 'maxId' => 2], 'notEmpty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.lead_id BETWEEN 1 and 2)'];
yield [['minId' => 1], 'notEmpty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.lead_id >= 1)'];
yield [['maxId' => 2], 'notEmpty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.lead_id <= 2)'];
yield [['lead_id' => 1], 'notEmpty', '1', 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE par1.lead_id = 1)'];
yield [['minId' => 1, 'maxId' => 2], 'like', '%.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url LIKE '%.com'))"];
yield [['minId' => 1], 'like', '%.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url LIKE '%.com'))"];
yield [['maxId' => 2], 'like', '%.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url LIKE '%.com'))"];
yield [['lead_id' => 1], 'like', '%.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url LIKE '%.com'))"];
yield [['minId' => 1, 'maxId' => 2], 'contains', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url LIKE '%.com%'))"];
yield [['minId' => 1], 'contains', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url LIKE '%.com%'))"];
yield [['maxId' => 2], 'contains', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url LIKE '%.com%'))"];
yield [['lead_id' => 1], 'contains', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url LIKE '%.com%'))"];
yield [['minId' => 1, 'maxId' => 2], 'startsWith', 'https://', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url LIKE 'https://%'))"];
yield [['minId' => 1], 'startsWith', 'https://', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url LIKE 'https://%'))"];
yield [['maxId' => 2], 'startsWith', 'https://', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url LIKE 'https://%'))"];
yield [['lead_id' => 1], 'startsWith', 'https://', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url LIKE 'https://%'))"];
yield [['minId' => 1, 'maxId' => 2], 'endsWith', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id BETWEEN 1 and 2) AND (par1.url LIKE '%.com'))"];
yield [['minId' => 1], 'endsWith', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id >= 1) AND (par1.url LIKE '%.com'))"];
yield [['maxId' => 2], 'endsWith', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id <= 2) AND (par1.url LIKE '%.com'))"];
yield [['lead_id' => 1], 'endsWith', '.com', "SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par1.lead_id FROM __PREFIX__page_hits par1 WHERE (par1.lead_id = 1) AND (par1.url LIKE '%.com'))"];
}
/**
* @param array<string, mixed> $batchLimiters
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQueryWithBatchFilters')]
public function testApplyQueryWithBatchFilters(array $batchLimiters, string $operator, string $parameterValue, string $expectedQuery): void
{
$expectedQuery = str_replace('__PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
$queryBuilder = new QueryBuilder($this->connectionMock);
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->getContactSegmentFilter([
'object' => 'behaviors',
'glue' => 'and',
'field' => 'hit_url',
'type' => 'text',
'operator' => $operator,
'properties' => [
'filter' => $parameterValue,
],
'filter' => null,
'display' => null,
], $batchLimiters);
$this->queryBuilder->applyQuery($queryBuilder, $filter);
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @return array<mixed>
*/
public static function dataApplyQueryAdditionalFiltersWithBatchLimiters(): iterable
{
yield [['minId' => 1, 'maxId' => 2], 'in', [1, 2], 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par2.lead_id FROM __PREFIX__lead_categories par2 WHERE (par2.lead_id BETWEEN 1 and 2) AND (par2.category_id IN (1, 2)))'];
yield [['minId' => 1], 'in', [1, 2], 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par2.lead_id FROM __PREFIX__lead_categories par2 WHERE (par2.lead_id >= 1) AND (par2.category_id IN (1, 2)))'];
yield [['maxId' => 2], 'in', [1, 2], 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par2.lead_id FROM __PREFIX__lead_categories par2 WHERE (par2.lead_id <= 2) AND (par2.category_id IN (1, 2)))'];
yield [['lead_id' => 1], 'in', [1, 2], 'SELECT 1 FROM __PREFIX__leads l WHERE l.id IN (SELECT par2.lead_id FROM __PREFIX__lead_categories par2 WHERE (par2.lead_id = 1) AND (par2.category_id IN (1, 2)))'];
}
/**
* @param array<string, mixed> $batchLimiters
* @param array<string, mixed> $parameterValue
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataApplyQueryAdditionalFiltersWithBatchLimiters')]
public function testApplyQueryAdditionalFiltersWithBatchLimiters(array $batchLimiters, string $operator, array $parameterValue, string $expectedQuery): void
{
$expectedQuery = str_replace('__PREFIX__', MAUTIC_TABLE_PREFIX, $expectedQuery);
$queryBuilder = new QueryBuilder($this->connectionMock);
$queryBuilder->select('1');
$queryBuilder->from(MAUTIC_TABLE_PREFIX.'leads', 'l');
$filter = $this->getContactSegmentFilter([
'glue' => 'and',
'field' => 'globalcategory',
'object' => 'lead',
'type' => 'globalcategory',
'operator' => $operator,
'properties' => [
'filter' => $parameterValue,
],
], $batchLimiters);
$this->queryBuilder->applyQuery($queryBuilder, $filter);
Assert::assertSame($expectedQuery, $queryBuilder->getDebugOutput());
}
/**
* @param array<string, mixed> $filter
* @param array<string, mixed> $batchLimiters
*/
private function getContactSegmentFilter(array $filter, array $batchLimiters = []): ContactSegmentFilter
{
return new ContactSegmentFilter(
new ContactSegmentFilterCrate($filter),
new CustomMappedDecorator(
new ContactSegmentFilterOperator(
new FilterOperatorProvider($this->dispatcher, $this->createMock(TranslatorInterface::class))
),
new ContactSegmentFilterDictionary($this->dispatcher)
),
new TableSchemaColumnsCache($this->createMock(EntityManager::class)),
$this->queryBuilder,
$batchLimiters
);
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Query\Filter;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\CoreBundle\Tests\Functional\CreateTestEntitiesTrait;
use PHPUnit\Framework\Assert;
class SegmentReferenceFilterQueryBuilderGlueTest extends MauticMysqlTestCase
{
use CreateTestEntitiesTrait;
public function testMultipleFiltersConnectedWithOrGlue(): void
{
$leadA = $this->createLead('A');
$leadB = $this->createLead('B');
$leadC = $this->createLead('C');
$leadD = $this->createLead('D');
$segmentA = $this->createSegment('A', []);
$this->createListLead($segmentA, $leadA);
$this->createListLead($segmentA, $leadD);
$segmentB = $this->createSegment('B', []);
$this->createListLead($segmentB, $leadB);
$this->createListLead($segmentB, $leadD);
$segmentC = $this->createSegment('C', []);
$this->createListLead($segmentC, $leadC);
$this->createListLead($segmentC, $leadD);
$this->em->flush();
$segmentD = $this->createSegment('D', [
[
'object' => 'lead',
'glue' => 'and',
'field' => 'leadlist',
'type' => 'leadlist',
'operator' => 'in',
'properties' => [
'filter' => [
$segmentA->getId(),
],
],
],
[
'object' => 'lead',
'glue' => 'or',
'field' => 'leadlist',
'type' => 'leadlist',
'operator' => 'in',
'properties' => [
'filter' => [
$segmentB->getId(),
$segmentC->getId(),
],
],
],
]);
$this->em->flush();
$this->em->clear();
$this->testSymfonyCommand('mautic:segments:update', ['--list-id' => $segmentD->getId()]);
$listModel = static::getContainer()->get('mautic.lead.model.list');
$leadCount = $listModel->getListLeadRepository()->getContactsCountBySegment($segmentD->getId());
Assert::assertSame(4, $leadCount, 'Segment must contain all the leads.');
}
}

View File

@@ -0,0 +1,513 @@
<?php
/** @noinspection SqlResolve SqlAggregates */
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment\Query;
use Doctrine\DBAL\ArrayParameterType;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Driver;
use Doctrine\DBAL\Platforms\MySQLPlatform;
use Mautic\LeadBundle\Segment\Query\Expression\ExpressionBuilder;
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
use Mautic\LeadBundle\Segment\Query\QueryException;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class QueryBuilderTest extends TestCase
{
private QueryBuilder $queryBuilder;
private Connection $connection;
protected function setUp(): void
{
$this->connection = $this->createConnectionFake();
$this->queryBuilder = new QueryBuilder($this->connection);
}
public function testExpr(): void
{
$expr = $this->queryBuilder->expr();
Assert::assertInstanceOf(ExpressionBuilder::class, $expr);
Assert::assertSame($expr, $this->queryBuilder->expr());
}
public function testSetParameter(): void
{
$queryBuilder = $this->queryBuilder->setParameter('one', 'first');
Assert::assertSame($queryBuilder, $this->queryBuilder);
$this->queryBuilder->setParameter('two', true);
$this->queryBuilder->setParameter('three', false);
$this->queryBuilder->setParameter(4, 'fourth');
Assert::assertSame([
'one' => 'first',
'two' => 1,
'three' => 0,
4 => 'fourth',
], $this->queryBuilder->getParameters());
}
public function testSetQueryPart(): void
{
$this->queryBuilder->select('t.name', 't.enabled')
->distinct()
->from('table1', 't')
->leftJoin('t', 'table2', 'j', 't.id = j.tid')
->where('t.enabled = 1');
$queryBuilder = $this->queryBuilder->setQueryPart('select', 't.name');
Assert::assertSame($queryBuilder, $this->queryBuilder);
$this->queryBuilder->setQueryPart('where', 't.enabled = 0');
$this->queryBuilder->setQueryPart('groupBy', 'j.code');
$this->queryBuilder->setQueryPart('distinct', null);
$this->assertSQL('SELECT t.name FROM table1 t LEFT JOIN table2 j ON t.id = j.tid WHERE t.enabled = 0 GROUP BY j.code');
}
public function testGetSQLSelectSimple(): void
{
$this->queryBuilder->select('1')
->from('table1');
$this->assertSQL('SELECT 1 FROM table1', 2);
}
public function testGetSQLSelectComplex(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->leftJoin('t', 'table2', 'j', 't.id = j.fid')
->where('t.enabled = 1')
->groupBy('t.type')
->having('t.salary > 5000')
->orderBy('t.id', 'DESC')
->setFirstResult(30)
->setMaxResults(10);
$this->assertSQL('SELECT t.name FROM table1 t LEFT JOIN table2 j ON t.id = j.fid WHERE t.enabled = 1 GROUP BY t.type HAVING t.salary > 5000 ORDER BY t.id DESC LIMIT 10 OFFSET 30', 2);
}
public function testGetSQLSelectHint(): void
{
$this->queryBuilder->select('1')
->add('from', [
'table' => 'table1',
'alias' => 't',
'hint' => 'USE INDEX (`PRIMARY`)',
], true)
->where('t.enabled = 0');
$this->assertSQL('SELECT 1 FROM table1 t USE INDEX (`PRIMARY`) WHERE t.enabled = 0', 2);
}
public function testGetSQLInsert(): void
{
$this->queryBuilder->insert('table1')
->values(['name' => 'Jack', 'enabled' => 1]);
$this->assertSQL('INSERT INTO table1 (name, enabled) VALUES(Jack, 1)', 2);
}
public function testGetSQLUpdate(): void
{
$this->queryBuilder->update('table1')
->set('enabled', '1')
->where('enabled = 0');
$this->assertSQL('UPDATE table1 SET enabled = 1 WHERE enabled = 0', 2);
}
public function testGetSQLDelete(): void
{
$this->queryBuilder->delete('table1')
->where('enabled = 1');
$this->assertSQL('DELETE FROM table1 WHERE enabled = 1', 2);
}
public function testGetJoinCondition(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 'l')
->leftJoin('l', 'table2', 'j', 'l.id = j.fid');
Assert::assertSame('l.id = j.fid', $this->queryBuilder->getJoinCondition('j'));
Assert::assertFalse($this->queryBuilder->getJoinCondition('k'));
}
public function testAddJoinCondition(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->leftJoin('t', 'table2', 'j', 't.id = j.fid');
$this->queryBuilder->addJoinCondition('j', $this->queryBuilder->expr()->eq('j.removed', 1));
$this->assertSQL('SELECT t.name FROM table1 t LEFT JOIN table2 j ON t.id = j.fid and (j.removed = 1)');
}
public function testAddJoinConditionNonExistentJoin(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->leftJoin('t', 'table2', 'j', 't.id = j.fid');
$this->expectException(QueryException::class);
$this->expectExceptionMessage('Inserting condition to nonexistent join x');
$this->queryBuilder->addJoinCondition('x', $this->queryBuilder->expr()->eq('j.removed', 1));
}
public function testReplaceJoinCondition(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 'l')
->leftJoin('l', 'table2', 'j', 'l.id = j.fid');
$this->queryBuilder->replaceJoinCondition('j', $this->queryBuilder->expr()->eq('j.removed', 1));
$this->assertSQL('SELECT t.name FROM table1 l LEFT JOIN table2 j ON j.removed = 1');
}
public function testSetParametersPairsNonArray(): void
{
$queryBuilder = $this->queryBuilder->setParametersPairs('one', 'first');
Assert::assertSame($queryBuilder, $this->queryBuilder);
$this->queryBuilder->setParametersPairs('two', 'second');
$this->queryBuilder->setParametersPairs('three', 'third');
Assert::assertSame([
'one' => 'first',
'two' => 'second',
'three' => 'third',
], $this->queryBuilder->getParameters());
}
public function testSetParametersPairsWithArray(): void
{
$queryBuilder = $this->queryBuilder->setParametersPairs(['one', 'three', 'five'], ['first', 'third', 'fifth']);
Assert::assertSame($queryBuilder, $this->queryBuilder);
Assert::assertSame([
'one' => 'first',
'three' => 'third',
'five' => 'fifth',
], $this->queryBuilder->getParameters());
}
public function testGetTableAlias(): void
{
$this->queryBuilder->select('1')
->from('tableFrom', 'f')
->leftJoin('f', 'leftJoinTable', 'l', 'f.id = l.fid')
->rightJoin('l', 'rightJoinTable', 'r', 'l.id = r.lid')
->innerJoin('f', 'innerJoinTable', 'i', 'f.id = i.fid')
->where('t.enabled = 1')
->groupBy('t.type')
->having('t.salary > 5000')
->orderBy('t.id', 'DESC')
->setFirstResult(30)
->setMaxResults(10);
Assert::assertFalse($this->queryBuilder->getTableAlias('nonExistent'));
Assert::assertFalse($this->queryBuilder->getTableAlias('nonExistent', 'inner'));
Assert::assertFalse($this->queryBuilder->getTableAlias('nonExistent', 'left'));
Assert::assertFalse($this->queryBuilder->getTableAlias('nonExistent', 'right'));
Assert::assertSame('f', $this->queryBuilder->getTableAlias('tableFrom'));
Assert::assertFalse($this->queryBuilder->getTableAlias('tableFrom', 'inner'));
Assert::assertFalse($this->queryBuilder->getTableAlias('tableFrom', 'left'));
Assert::assertFalse($this->queryBuilder->getTableAlias('tableFrom', 'right'));
Assert::assertSame('l', $this->queryBuilder->getTableAlias('leftJoinTable'));
Assert::assertFalse($this->queryBuilder->getTableAlias('leftJoinTable', 'inner'));
Assert::assertSame('l', $this->queryBuilder->getTableAlias('leftJoinTable', 'left'));
Assert::assertFalse($this->queryBuilder->getTableAlias('leftJoinTable', 'right'));
Assert::assertSame('r', $this->queryBuilder->getTableAlias('rightJoinTable'));
Assert::assertFalse($this->queryBuilder->getTableAlias('rightJoinTable', 'inner'));
Assert::assertFalse($this->queryBuilder->getTableAlias('rightJoinTable', 'left'));
Assert::assertSame('r', $this->queryBuilder->getTableAlias('rightJoinTable', 'right'));
Assert::assertSame('i', $this->queryBuilder->getTableAlias('innerJoinTable'));
Assert::assertSame('i', $this->queryBuilder->getTableAlias('innerJoinTable', 'inner'));
Assert::assertFalse($this->queryBuilder->getTableAlias('innerJoinTable', 'left'));
Assert::assertFalse($this->queryBuilder->getTableAlias('innerJoinTable', 'right'));
}
public function testGetTableJoins(): void
{
$this->queryBuilder->select('1')
->from('tableFrom', 'f')
->leftJoin('f', 'leftJoinTable', 'l', 'f.id = l.fid')
->rightJoin('l', 'rightJoinTable', 'r', 'l.id = r.lid')
->innerJoin('f', 'innerJoinTable', 'i', 'f.id = i.fid')
->innerJoin('f', 'innerJoinTable', 'i2', 'f.id = i2.fid')
->where('t.enabled = 1')
->groupBy('t.type')
->having('t.salary > 5000')
->orderBy('t.id', 'DESC')
->setFirstResult(30)
->setMaxResults(10);
Assert::assertSame([], $this->queryBuilder->getTableJoins('nonExistent'));
Assert::assertSame([], $this->queryBuilder->getTableJoins('tableFrom'));
Assert::assertSame([
[
'joinType' => 'left',
'joinTable' => 'leftJoinTable',
'joinAlias' => 'l',
'joinCondition' => 'f.id = l.fid',
],
], $this->queryBuilder->getTableJoins('leftJoinTable'));
Assert::assertSame([
[
'joinType' => 'right',
'joinTable' => 'rightJoinTable',
'joinAlias' => 'r',
'joinCondition' => 'l.id = r.lid',
],
], $this->queryBuilder->getTableJoins('rightJoinTable'));
Assert::assertSame([
[
'joinType' => 'inner',
'joinTable' => 'innerJoinTable',
'joinAlias' => 'i',
'joinCondition' => 'f.id = i.fid',
],
[
'joinType' => 'inner',
'joinTable' => 'innerJoinTable',
'joinAlias' => 'i2',
'joinCondition' => 'f.id = i2.fid',
],
], $this->queryBuilder->getTableJoins('innerJoinTable'));
}
public function testGuessPrimaryLeadContactIdColumnWithOrphanedLeads(): void
{
$this->queryBuilder->select('1')
->from('lead_lists_leads', 'orp');
Assert::assertSame('orp.lead_id', $this->queryBuilder->guessPrimaryLeadContactIdColumn());
}
public function testGuessPrimaryLeadContactIdColumnWithoutJoins(): void
{
$this->queryBuilder->select('1')
->from('leads', 'l');
Assert::assertSame('l.id', $this->queryBuilder->guessPrimaryLeadContactIdColumn());
}
public function testGuessPrimaryLeadContactIdColumnWithNonRightJoin(): void
{
$this->queryBuilder->select('1')
->from('leads', 'l')
->leftJoin('l', 'leftJoinTable', 'lj', 'l.id = lj.lid')
->innerJoin('l', 'innerJoinTable', 'ij', 'l.id = ij.lid');
Assert::assertSame('l.id', $this->queryBuilder->guessPrimaryLeadContactIdColumn());
}
public function testGuessPrimaryLeadContactIdColumnWithNonMatchingRightJoin(): void
{
$this->queryBuilder->select('1')
->from('leads', 'l')
->rightJoin('l', 'rightJoinTable', 'r', 'l.name = r.name');
Assert::assertSame('l.id', $this->queryBuilder->guessPrimaryLeadContactIdColumn());
}
public function testGuessPrimaryLeadContactIdColumnWithMatchingRightJoin(): void
{
$this->queryBuilder->select('1')
->from('leads', 'l')
->rightJoin('l', 'rightJoinTable', 'r', 'l.id = r.lid');
Assert::assertSame('r.lid', $this->queryBuilder->guessPrimaryLeadContactIdColumn());
}
public function testIsJoinTable(): void
{
$this->queryBuilder->select('1')
->from('leads', 'l')
->leftJoin('l', 'leftJoinTable', 'lj', 'l.id = lj.lid')
->rightJoin('l', 'rightJoinTable', 'rj', 'l.id = rj.lid')
->innerJoin('l', 'innerJoinTable', 'ij', 'l.id = ij.lid');
Assert::assertFalse($this->queryBuilder->isJoinTable('nonExistent'));
Assert::assertFalse($this->queryBuilder->isJoinTable('leads'));
Assert::assertTrue($this->queryBuilder->isJoinTable('leftJoinTable'));
Assert::assertTrue($this->queryBuilder->isJoinTable('rightJoinTable'));
Assert::assertTrue($this->queryBuilder->isJoinTable('innerJoinTable'));
}
public function testGetDebugOutput(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->leftJoin('t', 'table2', 'j', 't.id = j.fid')
->where('t.enabled = :enabled')
->andWhere('t.state IN (:states)')
->groupBy('t.type')
->having('t.salary > :salary AND t.flag = :flag')
->orderBy('t.id', 'DESC')
->setParameter('enabled', true)
->setParameter('salary', 5000)
->setParameter('states', ['new', 'active'], ArrayParameterType::STRING)
->setParameter('flag', 'internal')
->setFirstResult(30)
->setMaxResults(10);
Assert::assertSame("SELECT t.name FROM table1 t LEFT JOIN table2 j ON t.id = j.fid WHERE (t.enabled = 1) AND (t.state IN ('new', 'active')) GROUP BY t.type HAVING t.salary > 5000 AND t.flag = 'internal' ORDER BY t.id DESC LIMIT 10 OFFSET 30", $this->queryBuilder->getDebugOutput());
}
public function testHasLogicStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
Assert::assertFalse($this->queryBuilder->hasLogicStack());
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
Assert::assertTrue($this->queryBuilder->hasLogicStack());
}
public function testGetLogicStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
Assert::assertSame([], $this->queryBuilder->getLogicStack());
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->lt('a.salary', 3000), 'AND');
Assert::assertSame([
'a.name = John',
'a.salary < 3000',
], $this->queryBuilder->getLogicStack());
}
public function testPopLogicStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->lt('a.salary', 3000), 'AND');
Assert::assertSame([
'a.name = John',
'a.salary < 3000',
], $this->queryBuilder->popLogicStack());
Assert::assertSame([], $this->queryBuilder->getLogicStack());
}
public function testAddLogicOrWithEmptyWhere(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
Assert::assertSame([], $this->queryBuilder->getLogicStack());
$this->assertSQL('SELECT t.name FROM table1 t WHERE a.name = John');
$this->queryBuilder->applyStackLogic();
$this->assertSQL('SELECT t.name FROM table1 t WHERE a.name = John');
}
public function testAddLogicOrWithExistingWhereWithEmptyStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
Assert::assertSame(['a.name = John'], $this->queryBuilder->getLogicStack());
$this->assertSQL('SELECT t.name FROM table1 t WHERE t.enabled = 1');
$this->queryBuilder->applyStackLogic();
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) OR (a.name = John)');
}
public function testAddLogicOrWithExistingWhereWithExistingStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.flag', 'active'), 'OR');
Assert::assertSame(['a.flag = active'], $this->queryBuilder->getLogicStack());
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) OR (a.name = John)');
$this->queryBuilder->applyStackLogic();
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) OR (a.name = John) OR (a.flag = active)');
}
public function testAddLogicAndWithEmptyWhere(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'AND');
Assert::assertSame([], $this->queryBuilder->getLogicStack());
$this->assertSQL('SELECT t.name FROM table1 t WHERE a.name = John');
$this->queryBuilder->applyStackLogic();
$this->assertSQL('SELECT t.name FROM table1 t WHERE a.name = John');
}
public function testAddLogicAndWithExistingWhereWithEmptyStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'AND');
Assert::assertSame([], $this->queryBuilder->getLogicStack());
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) AND (a.name = John)');
$this->queryBuilder->applyStackLogic();
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) AND (a.name = John)');
}
public function testAddLogicAndWithExistingWhereWithExistingStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'OR');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.flag', 'active'), 'AND');
Assert::assertSame([
'a.name = John',
'a.flag = active',
], $this->queryBuilder->getLogicStack());
$this->assertSQL('SELECT t.name FROM table1 t WHERE t.enabled = 1');
$this->queryBuilder->applyStackLogic();
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) OR ((a.name = John) AND (a.flag = active))');
}
public function testApplyStackLogicWithEmptyStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$queryBuilder = $this->queryBuilder->applyStackLogic();
Assert::assertSame($queryBuilder, $this->queryBuilder);
$this->assertSQL('SELECT t.name FROM table1 t WHERE t.enabled = 1');
}
public function testApplyStackLogicWithExistingStack(): void
{
$this->queryBuilder->select('t.name')
->from('table1', 't')
->where('t.enabled = 1');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.name', 'John'), 'AND');
$this->queryBuilder->addLogic($this->queryBuilder->expr()->eq('a.flag', 'active'), 'AND');
$queryBuilder = $this->queryBuilder->applyStackLogic();
Assert::assertSame($queryBuilder, $this->queryBuilder);
$this->assertSQL('SELECT t.name FROM table1 t WHERE (t.enabled = 1) AND (a.name = John) AND (a.flag = active)');
}
private function assertSQL(string $sql, int $repeat = 1): void
{
for ($i = 0; $i < $repeat; ++$i) {
Assert::assertSame($sql, $this->queryBuilder->getSQL());
}
}
private function createConnectionFake(): Connection
{
return new class([], $this->createMock(Driver::class)) extends Connection {
public function getDatabasePlatform()
{
return new MySQLPlatform();
}
};
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment;
use Mautic\LeadBundle\Segment\RandomParameterName;
use PHPUnit\Framework\TestCase;
class RandomParameterNameTest extends TestCase
{
public function testGenerateRandomParameterName(): void
{
$generator = new RandomParameterName();
$expectedValues = [
'par0',
'par1',
'par2',
'par3',
'par4',
'par5',
'par6',
'par7',
'par8',
'par9',
'para',
'parb',
'parc',
'pard',
'pare',
'parf',
'parg',
'parh',
'pari',
'parj',
'park',
'parl',
'parm',
'parn',
'paro',
'parp',
'parq',
'parr',
'pars',
'part',
'paru',
'parv',
'parw',
'parx',
'pary',
'parz',
'par10',
'par11',
];
foreach ($expectedValues as $expectedValue) {
self::assertSame($expectedValue, $generator->generateRandomParameterName());
}
}
}

View File

@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
namespace Mautic\LeadBundle\Tests\Segment;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadField;
use Mautic\LeadBundle\Entity\LeadList;
use Mautic\LeadBundle\Model\FieldModel;
use Mautic\LeadBundle\Segment\ContactSegmentService;
class SegmentFilterFunctionalTest extends MauticMysqlTestCase
{
/**
* @var Lead[]
*/
private $leads = [];
protected $useCleanupRollback = false;
/**
* Test creates: contacts, segment
* Test rebuilds segment
* Test check that the right contacts are in the segment.
*/
public function testSegments(): void
{
$this->createCustomMultiselectField();
foreach ($this->getSegmentsProvider() as $scenario) {
$this->runTestSegments($scenario['contacts'], $scenario['segment']);
}
}
/**
* @param mixed[] $contacts
* @param mixed[] $segment
*/
private function runTestSegments(array $contacts, array $segment): void
{
$countInSegment = $this->createLeads($contacts);
$leadList = $this->createSegment($segment);
$this->buildSegment($leadList, $countInSegment);
$this->cleanAfterTest($leadList);
}
/**
* @param mixed[] $contacts
*/
private function createLeads(array $contacts): int
{
$countInSegment = 0;
foreach ($contacts as $contact) {
$lead = $this->createLead($contact);
$this->em->persist($lead);
$this->leads[] = $lead;
if ($contact['in_segment']) {
++$countInSegment;
}
}
$this->em->flush();
return $countInSegment;
}
/**
* @param mixed[] $values
*/
private function createLead(array $values): Lead
{
$lead = new Lead();
foreach ($values as $field => $value) {
if ('in_segment' === $field) {
continue;
}
call_user_func_array([$lead, 'set'.$field], [$value]);
}
return $lead;
}
/**
* @param mixed[] $segmentFilters
*/
private function createSegment(array $segmentFilters): LeadList
{
$filters = [];
foreach ($segmentFilters as $segmentFilter) {
$filters[] = [
'object' => 'lead',
'glue' => $segmentFilter['glue'],
'field' => $segmentFilter['field'],
'type' => $segmentFilter['type'],
'properties' => ['filter' => $segmentFilter['value']],
'operator' => $segmentFilter['operator'],
];
}
$payload = [
'name' => 'API segment',
'alias' => 'api_segment_test',
'description' => 'Segment created via API',
'filters' => $filters,
];
// Create:
$this->client->request('POST', '/api/segments/new', $payload);
$clientResponse = $this->client->getResponse();
$response = json_decode($clientResponse->getContent(), true);
if (!empty($response['errors'][0])) {
$this->fail($response['errors'][0]['code'].': '.$response['errors'][0]['message']);
}
$segmentId = $response['list']['id'];
$this->assertSame(201, $clientResponse->getStatusCode());
$this->assertGreaterThan(0, $segmentId);
return $this->em->getRepository(LeadList::class)->find($segmentId);
}
private function buildSegment(LeadList $segment, int $expectedCountInSegment): void
{
/** @var ContactSegmentService $contactSegmentService */
$contactSegmentService = static::getContainer()->get('mautic.lead.model.lead_segment_service');
$this->testSymfonyCommand('mautic:segments:update', [
'-i' => $segment->getId(),
'--env' => 'test',
]);
$segmentContacts = $contactSegmentService->getTotalLeadListLeadsCount($segment);
$this->assertEquals(
$expectedCountInSegment,
$segmentContacts[$segment->getId()]['count']
);
}
private function cleanAfterTest(LeadList $segment): void
{
$this->em->remove($segment);
foreach ($this->leads as $lead) {
$deleteLead = $this->em->getRepository(Lead::class)->find($lead->getId());
$this->em->remove($deleteLead);
}
$this->em->flush();
$this->leads = [];
}
/**
* @see self::testSegments
*
* @return \Generator<int,mixed>
*/
private function getSegmentsProvider(): \Generator
{
yield [
'contacts' => [
['email' => 'lukas@mautic.com', 'in_segment' => true, 'city' => 'Prague'],
['email' => 'lukas2@mautic.com', 'in_segment' => true, 'city' => 'Prague 11'],
['email' => 'lukas3@mautic.com', 'in_segment' => false, 'city' => 'Praha'],
],
'segment' => [
['field' => 'city', 'operator' => 'startsWith', 'value' => 'Prague', 'glue' => 'and', 'type' => 'text'],
],
];
yield [
'contacts' => [
['email' => 'lukas@mautic.com', 'in_segment' => true, 'points' => 20],
['email' => 'lukas2@mautic.com', 'in_segment' => false, 'points' => 10],
['email' => 'lukas3@mautic.com', 'in_segment' => true, 'points' => 25],
],
'segment' => [
['field' => 'points', 'operator' => 'gte', 'value' => 20, 'glue' => 'and', 'type' => 'text'],
],
];
yield [
'contacts' => [
['email' => 'lukas@mautic.com', 'in_segment' => true],
],
'segment' => [
['field' => 'multiselect', 'object' => 'lead', 'operator' => '!in', 'value' => ['s'], 'glue' => 'and', 'type' => 'multiselect'],
],
];
}
protected function createCustomMultiselectField(): void
{
$field = new LeadField();
$field->setType('multiselect');
$field->setObject('lead');
$field->setAlias('multiselect');
$field->setName('Multiselect');
$properties = unserialize('a:1:{s:4:"list";a:3:{i:0;a:2:{s:5:"label";s:1:"f";s:5:"value";s:1:"f";}i:1;a:2:{s:5:"label";s:1:"s";s:5:"value";s:1:"s";}i:2;a:2:{s:5:"label";s:1:"t";s:5:"value";s:1:"t";}}}');
$field->setProperties($properties);
$fieldModel = self::getContainer()->get(FieldModel::class);
$fieldModel->saveEntity($field);
}
}