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,314 @@
<?php
declare(strict_types=1);
namespace Mautic\ConfigBundle\Tests\Controller;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use PHPUnit\Framework\Assert;
use Symfony\Component\DomCrawler\Field\ChoiceFormField;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
class ConfigControllerFunctionalTest extends MauticMysqlTestCase
{
private const SUBDOMAIN_URL = 'subdomain_url.com';
private string $prefix;
protected $useCleanupRollback = false;
protected function setUp(): void
{
$this->configParams['config_allowed_parameters'] = [
'kernel.project_dir',
];
$this->configParams['locale'] = 'en_US';
$this->configParams['subdomain_url'] = self::SUBDOMAIN_URL;
parent::setUp();
$this->prefix = MAUTIC_TABLE_PREFIX;
}
public function testValuesAreEscapedProperly(): void
{
$trackIps = "%ip1%\n%ip2%\n%kernel.project_dir%";
$googleAnalytics = 'reveal pass: %mautic.db_password%';
// request config edit page
$crawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$this->assertResponseIsSuccessful();
// Find save & close button
$buttonCrawler = $crawler->selectButton('config[buttons][save]');
$form = $buttonCrawler->form();
$form->setValues(
[
'config[coreconfig][site_url]' => 'https://mautic-community.local', // required
'config[coreconfig][do_not_track_ips]' => $trackIps,
'config[pageconfig][google_analytics]' => $googleAnalytics,
'config[leadconfig][contact_columns]' => ['name', 'email', 'id'],
]
);
$crawler = $this->client->submit($form);
$this->assertResponseIsSuccessful();
// Check for a flash error
$response = $this->client->getResponse()->getContent();
$message = $crawler->filterXPath("//div[@id='flashes']//span")->count()
?
$crawler->filterXPath("//div[@id='flashes']//span")->first()->text()
:
'';
Assert::assertStringNotContainsString('Could not save updated configuration:', $response, $message);
// Check values are escaped properly in the config file
$configParameters = $this->getConfigParameters();
Assert::assertArrayHasKey('do_not_track_ips', $configParameters);
Assert::assertSame(
[
$this->escape('%ip1%'),
$this->escape('%ip2%'),
'%kernel.project_dir%',
],
$configParameters['do_not_track_ips']
);
Assert::assertArrayHasKey('google_analytics', $configParameters);
Assert::assertSame($this->escape($googleAnalytics), $configParameters['google_analytics']);
// Check values are unescaped properly in the edit form
$crawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$this->assertResponseIsSuccessful();
$buttonCrawler = $crawler->selectButton('config[buttons][save]');
$form = $buttonCrawler->form();
Assert::assertEquals($trackIps, $form['config[coreconfig][do_not_track_ips]']->getValue());
Assert::assertEquals($googleAnalytics, $form['config[pageconfig][google_analytics]']->getValue());
}
private function getConfigPath(): string
{
return static::getContainer()->get('kernel')->getLocalConfigFile();
}
private function getConfigParameters(): array
{
$parameters = [];
include $this->getConfigPath();
return $parameters;
}
private function escape(string $value): string
{
return str_replace('%', '%%', $value);
}
public function testConfigNotFoundPageConfiguration(): void
{
// insert published record
$this->connection->insert($this->prefix.'pages', [
'is_published' => 1,
'date_added' => (new \DateTime())->format('Y-m-d H:i:s'),
'title' => 'page1',
'alias' => 'page1',
'template' => 'blank',
'custom_html' => 'Page1 Test Html',
'hits' => 0,
'unique_hits' => 0,
'variant_hits' => 0,
'revision' => 0,
'lang' => 'en',
]);
$page1 = $this->connection->lastInsertId();
// insert unpublished record
$this->connection->insert($this->prefix.'pages', [
'is_published' => 0,
'date_added' => (new \DateTime())->format('Y-m-d H:i:s'),
'title' => 'page2',
'alias' => 'page2',
'template' => 'blank',
'custom_html' => 'Page2 Test Html',
'hits' => 0,
'unique_hits' => 0,
'variant_hits' => 0,
'revision' => 0,
'lang' => 'en',
]);
$this->connection->lastInsertId();
// insert published record
$this->connection->insert($this->prefix.'pages', [
'is_published' => 1,
'date_added' => (new \DateTime())->format('Y-m-d H:i:s'),
'title' => 'page3',
'alias' => 'page3',
'template' => 'blank',
'custom_html' => 'Page3 Test Html',
'hits' => 0,
'unique_hits' => 0,
'variant_hits' => 0,
'revision' => 0,
'lang' => 'en',
]);
$page3 = $this->connection->lastInsertId();
// request config edit page
$crawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
// Find save & close button
$buttonCrawler = $crawler->selectButton('config[buttons][save]');
$form = $buttonCrawler->form();
// Fetch available option for 404_page field
$availableOptions = $form['config[coreconfig][404_page]']->availableOptionValues();
// page 2 should not be available in option list because it is unpublished
$this->assertEquals(['', $page1, $page3], $availableOptions);
// page 3 for 404_page
$form->setValues(
[
'config[coreconfig][site_url]' => 'https://mautic-community.local', // required
'config[leadconfig][contact_columns]' => ['name', 'email', 'id'],
'config[coreconfig][404_page]' => $page3,
]
);
$crawler = $this->client->submit($form);
$this->assertResponseIsSuccessful();
$crawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$this->assertResponseIsSuccessful();
$buttonCrawler = $crawler->selectButton('config[buttons][save]');
$form = $buttonCrawler->form();
Assert::assertEquals($page3, $form['config[coreconfig][404_page]']->getValue());
// re-create the Symfony client to make config changes applied
$this->setUpSymfony($this->configParams);
// Request not found url page3 page content should be rendered
$crawler = $this->client->request(Request::METHOD_GET, '/notfoundurlblablabla');
$this->assertResponseStatusCodeSame(Response::HTTP_NOT_FOUND);
$this->assertStringContainsString('Page3 Test Html', $crawler->text());
}
public function testConfigNotificationConfiguration(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$buttonCrawler = $crawler->selectButton('config[buttons][save]');
$form = $buttonCrawler->form();
$send_notification_to_author = '0';
$campaign_notification_email_addresses = 'a@test.com, b@test.com';
$webhook_notification_email_addresses = 'a@webhook.com, b@webhook.com';
$form->setValues(
[
'config[coreconfig][site_url]' => 'https://mautic-community.local', // required
'config[leadconfig][contact_columns]' => ['name', 'email', 'id'],
'config[notification_config][campaign_send_notification_to_author]' => $send_notification_to_author,
'config[notification_config][campaign_notification_email_addresses]' => $campaign_notification_email_addresses,
'config[notification_config][webhook_send_notification_to_author]' => $send_notification_to_author,
'config[notification_config][webhook_notification_email_addresses]' => $webhook_notification_email_addresses,
]
);
$this->client->submit($form);
$this->assertResponseIsSuccessful();
$crawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$this->assertResponseIsSuccessful();
$buttonCrawler = $crawler->selectButton('config[buttons][save]');
$form = $buttonCrawler->form();
Assert::assertEquals($send_notification_to_author, $form['config[notification_config][campaign_send_notification_to_author]']->getValue());
Assert::assertEquals($campaign_notification_email_addresses, $form['config[notification_config][campaign_notification_email_addresses]']->getValue());
Assert::assertEquals($send_notification_to_author, $form['config[notification_config][webhook_send_notification_to_author]']->getValue());
Assert::assertEquals($webhook_notification_email_addresses, $form['config[notification_config][webhook_notification_email_addresses]']->getValue());
}
public function testUserAndSystemLocale(): void
{
// 1. Change user locale in account - should change _locale session
$accountCrawler = $this->client->request(Request::METHOD_GET, '/s/account');
$this->assertResponseIsSuccessful();
$accountSaveButton = $accountCrawler->selectButton('user[buttons][save]');
$accountForm = $accountSaveButton->form();
$accountForm->setValues(
[
'user[locale]' => 'en_US',
]
);
$this->client->submit($accountForm);
$this->assertResponseIsSuccessful();
Assert::assertSame('en_US', $this->client->getRequest()->getSession()->get('_locale'));
// 2. Change system locale in configuration - should not change _locale session
$configCrawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$configSaveButton = $configCrawler->selectButton('config[buttons][save]');
$configForm = $configSaveButton->form();
$configForm->setValues(
[
'config[coreconfig][locale]' => 'en_US',
'config[coreconfig][site_url]' => 'https://mautic-cloud.local', // required
]
);
$this->client->submit($configForm);
$this->assertResponseIsSuccessful();
Assert::assertSame('en_US', $this->client->getRequest()->getSession()->get('_locale'));
// 3. Change user locale to system default in account - should change _locale session to system default
$accountCrawler = $this->client->request(Request::METHOD_GET, '/s/account');
$accountSaveButton = $accountCrawler->selectButton('user[buttons][save]');
$accountForm = $accountSaveButton->form();
$accountForm->setValues(
[
'user[locale]' => '',
]
);
$this->client->submit($accountForm);
$this->assertResponseIsSuccessful();
Assert::assertSame('en_US', $this->client->getRequest()->getSession()->get('_locale'));
// 2. Change system locale in configuration to en_US - should change _locale session
$configCrawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$configSaveButton = $configCrawler->selectButton('config[buttons][save]');
$configForm = $configSaveButton->form();
$configForm->setValues(
[
'config[coreconfig][locale]' => 'en_US',
'config[coreconfig][site_url]' => 'https://mautic-cloud.local', // required
]
);
$this->client->submit($configForm);
$this->assertResponseIsSuccessful();
Assert::assertSame('en_US', $this->client->getRequest()->getSession()->get('_locale'));
}
public function testSSOSettingEntityId(): void
{
$configCrawler = $this->client->request(Request::METHOD_GET, '/s/config/edit');
$configSaveButton = $configCrawler->selectButton('config[buttons][apply]');
$configForm = $configSaveButton->form();
/** @var ChoiceFormField $entityIdField */
$entityIdField = $configForm['config[userconfig][saml_idp_entity_id]'];
$availableOptions = $entityIdField->availableOptionValues();
Assert::assertCount(3, $availableOptions);
$configForm->setValues(
[
'config[userconfig][saml_idp_entity_id]' => $availableOptions[1],
'config[coreconfig][site_url]' => 'https://mautic-cloud.local', // required
]
);
$this->client->submit($configForm);
$this->assertResponseIsSuccessful();
Assert::assertEquals($availableOptions[1], $configForm['config[userconfig][saml_idp_entity_id]']->getValue());
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Mautic\ConfigBundle\Tests\Controller;
use Mautic\ConfigBundle\Model\SysinfoModel;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use PHPUnit\Framework\Assert;
use Symfony\Component\HttpFoundation\Request;
class SysinfoControllerTest extends MauticMysqlTestCase
{
public function testDbInfoIsShown(): void
{
$sysinfoModel = static::getContainer()->get(SysinfoModel::class);
\assert($sysinfoModel instanceof SysinfoModel);
$dbInfo = $sysinfoModel->getDbInfo();
// Request sysinfo page
$crawler = $this->client->request(Request::METHOD_GET, '/s/sysinfo');
Assert::assertTrue($this->client->getResponse()->isOk());
$dbVersion = $crawler->filterXPath("//td[@id='dbinfo-version']")->text();
$dbDriver = $crawler->filterXPath("//td[@id='dbinfo-driver']")->text();
$dbPlatform = $crawler->filterXPath("//td[@id='dbinfo-platform']")->text();
$recommendations = $crawler->filter('#recommendations');
Assert::assertSame($dbInfo['version'], $dbVersion);
Assert::assertSame($dbInfo['driver'], $dbDriver);
Assert::assertSame($dbInfo['platform'], $dbPlatform);
Assert::assertGreaterThan(0, $recommendations->count());
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Mautic\ConfigBundle\Tests\Event;
use Mautic\ConfigBundle\Event\ConfigBuilderEvent;
use Mautic\CoreBundle\Tests\CommonMocks;
class ConfigBuilderEventTest extends CommonMocks
{
public function testAddForm(): void
{
$event = $this->initEvent();
$form = ['formAlias' => 'testform'];
$result = $event->addForm($form);
$this->assertTrue($result instanceof ConfigBuilderEvent);
$forms = $event->getForms();
$this->assertEquals($form, $forms[$form['formAlias']]);
}
public function testRemoveForm(): void
{
$event = $this->initEvent();
$form = ['formAlias' => 'testform'];
$event->addForm($form);
$result = $event->removeForm($form['formAlias']);
$forms = $event->getForms();
$this->assertEquals([], $forms);
$this->assertTrue($result);
}
protected function initEvent()
{
return new ConfigBuilderEvent($this->getBundleHelperMock());
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace Mautic\ConfigBundle\Tests\Event;
use Mautic\ConfigBundle\Event\ConfigEvent;
use Symfony\Component\HttpFoundation\File\UploadedFile;
use Symfony\Component\HttpFoundation\ParameterBag;
class ConfigEventTest extends \PHPUnit\Framework\TestCase
{
public function testGetSetConfig(): void
{
// Config not defined
$config = [];
$paramBag = $this->createMock(ParameterBag::class);
$event = new ConfigEvent($config, $paramBag);
$key = 'undefined';
$this->assertEquals([], $event->getConfig($key));
// Config defined with setter
$key = 'defined';
$config = ['config' => []];
$event->setConfig($config, $key);
$this->assertEquals($config, $event->getConfig($key));
// Config not found by key so complete config returned;
$undefinedKey = 'undefined';
$this->assertEquals([], $event->getConfig($undefinedKey));
// Get complete config
$config = [$key => $config];
$this->assertEquals($config, $event->getConfig());
}
public function testGetSetPreserved(): void
{
$config = [];
$paramBag = $this->createMock(ParameterBag::class);
$event = new ConfigEvent($config, $paramBag);
$this->assertEquals([], $event->getPreservedFields());
$preserved = 'preserved';
$result = [$preserved];
$event->unsetIfEmpty($preserved);
$this->assertEquals($result, $event->getPreservedFields());
$preserved = ['preserved' => 'value'];
$result = array_merge($result, $preserved);
$event->unsetIfEmpty($preserved);
$this->assertEquals($result, $event->getPreservedFields());
}
public function testGetSetErrors(): void
{
$config = [];
$paramBag = $this->createMock(ParameterBag::class);
$event = new ConfigEvent($config, $paramBag);
$this->assertEquals([], $event->getErrors());
$message = 'message';
$messages = [$message => []];
$this->assertEquals($event, $event->setError($message));
$this->assertEquals($messages, $event->getErrors());
$message = 'message';
$messageVars = ['var' => 'value'];
$messages = [$message => $messageVars];
$this->assertEquals($event, $event->setError($message, $messageVars));
$this->assertEquals($messages, $event->getErrors());
$message = 'message';
$messageVars = ['var' => 'value'];
$key = 'key';
$field = 'field';
$fieldErrors[$key][$field] = [
$message,
$messageVars,
];
$this->assertEquals($event, $event->setError($message, $messageVars, $key, $field));
$this->assertEquals($fieldErrors, $event->getFieldErrors());
}
public function testGetFileContent(): void
{
$config = [];
$paramBag = $this->createMock(ParameterBag::class);
$event = new ConfigEvent($config, $paramBag);
$fileContent = 'content';
$fileHandler = tmpfile();
$realPath = stream_get_meta_data($fileHandler)['uri'];
fwrite($fileHandler, ' '.$fileContent);
$uploadedFile = $this->createMock(UploadedFile::class);
$uploadedFile->expects($this->once())
->method('getRealPath')
->willReturn($realPath);
$this->assertEquals($fileContent, $event->getFileContent($uploadedFile));
$this->assertFalse(file_exists($realPath));
}
public function testEncodeFileContents(): void
{
$config = [];
$paramBag = $this->createMock(ParameterBag::class);
$event = new ConfigEvent($config, $paramBag);
$string = 'řčžýřžýčř';
$result = 'xZnEjcW+w73FmcW+w73EjcWZ';
$this->assertEquals($result, $event->encodeFileContents($string));
}
public function testNormalizedDataGetSet(): void
{
$config = [];
$paramBag = $this->createMock(ParameterBag::class);
$event = new ConfigEvent($config, $paramBag);
$origNormData = ['orig'];
$this->assertInstanceOf(ConfigEvent::class, $event->setOriginalNormData($origNormData));
$this->assertEquals($origNormData, $event->getOriginalNormData());
$normData = ['norm'];
$event->setNormData($normData);
$this->assertEquals($normData, $event->getNormData());
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Mautic\ConfigBundle\Tests\EventListener;
use Mautic\ConfigBundle\ConfigEvents;
use Mautic\ConfigBundle\Event\ConfigEvent;
use Mautic\ConfigBundle\EventListener\ConfigSubscriber;
use Mautic\ConfigBundle\Service\ConfigChangeLogger;
use Mautic\CoreBundle\Entity\AuditLogRepository;
use Mautic\CoreBundle\Entity\IpAddressRepository;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ConfigSubscriberTest extends TestCase
{
/**
* @var ConfigChangeLogger|MockObject
*/
private MockObject $logger;
private ConfigSubscriber $subscriber;
protected function setUp(): void
{
$this->logger = $this->createMock(ConfigChangeLogger::class);
$ipAddressRepo = $this->createMock(IpAddressRepository::class);
$coreParamHelper = $this->createMock(CoreParametersHelper::class);
$auditLogRepo = $this->createMock(AuditLogRepository::class);
$this->subscriber = new ConfigSubscriber($this->logger, $ipAddressRepo, $coreParamHelper, $auditLogRepo);
}
public function testGetSubscribedEvents(): void
{
$this->assertEquals(
[
ConfigEvents::CONFIG_POST_SAVE => ['onConfigPostSave', 0],
],
$this->subscriber->getSubscribedEvents()
);
}
public function testNothingToLogOnConfigPostSave(): void
{
// Test nothing to log
$this->logger->expects($this->never())
->method('log');
$event = $this->createMock(ConfigEvent::class);
$event->expects($this->once())
->method('getOriginalNormData')
->willReturn(null);
$this->subscriber->onConfigPostSave($event);
}
public function testSomethingToLogOnConfigPostSave(): void
{
// Test something to log
$originalNormData = ['orig'];
$normData = ['norm'];
$event = $this->createMock(ConfigEvent::class);
$event->expects($this->once())
->method('getOriginalNormData')
->willReturn($originalNormData);
$event->expects($this->once())
->method('getNormData')
->willReturn($normData);
$this->logger->expects($this->once())
->method('setOriginalNormData')
->with($originalNormData)
->willReturn($this->logger);
$this->logger->expects($this->once())
->method('log')
->with($normData);
$this->subscriber->onConfigPostSave($event);
}
}

View File

@@ -0,0 +1,257 @@
<?php
namespace Mautic\ConfigBundle\Tests\Form\Helper;
use Mautic\ConfigBundle\Form\DataTransformer\DsnTransformerFactory;
use Mautic\ConfigBundle\Form\Helper\RestrictionHelper;
use Mautic\ConfigBundle\Form\Type\ConfigType;
use Mautic\ConfigBundle\Form\Type\DsnType;
use Mautic\ConfigBundle\Form\Type\EscapeTransformer;
use Mautic\CoreBundle\Form\Type\ButtonGroupType;
use Mautic\CoreBundle\Form\Type\FormButtonsType;
use Mautic\CoreBundle\Form\Type\StandAloneButtonType;
use Mautic\CoreBundle\Form\Type\YesNoButtonGroupType;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Translation\Translator;
use Mautic\EmailBundle\EventListener\ProcessBounceSubscriber;
use Mautic\EmailBundle\EventListener\ProcessUnsubscribeSubscriber;
use Mautic\EmailBundle\Form\Type\ConfigMonitoredEmailType;
use Mautic\EmailBundle\Form\Type\ConfigMonitoredMailboxesType;
use Mautic\EmailBundle\Form\Type\ConfigType as EmailConfigType;
use Mautic\EmailBundle\MonitoredEmail\Mailbox;
use Mautic\EmailBundle\MonitoredEmail\Processor\Bounce;
use Mautic\EmailBundle\MonitoredEmail\Processor\FeedbackLoop;
use Mautic\EmailBundle\MonitoredEmail\Processor\Unsubscribe;
use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
use Symfony\Component\Form\Form;
use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\Forms;
use Symfony\Component\Form\PreloadedExtension;
use Symfony\Component\Form\Test\TypeTestCase;
use Symfony\Component\Validator\ConstraintViolationList;
use Symfony\Component\Validator\Mapping\ClassMetadata;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
* Mocking a representative ConfigForm by leveraging Symfony's TypeTestCase to test RestrictionHelper.
*/
#[\PHPUnit\Framework\Attributes\CoversClass(RestrictionHelper::class)]
class RestrictionHelperTest extends TypeTestCase
{
/**
* @var string
*/
private $displayMode = RestrictionHelper::MODE_REMOVE;
/**
* @var array
*/
private $restrictedFields = [
'monitored_email' => [
'EmailBundle_bounces',
'EmailBundle_unsubscribes' => [
'address',
],
],
];
private $forms = [
'emailconfig' => [
'bundle' => 'EmailBundle',
'formAlias' => 'emailconfig',
'formType' => EmailConfigType::class,
'formTheme' => 'MauticEmailBundle:FormTheme\\Config',
'parameters' => [
'mailer_from_name' => 'Mautic',
'mailer_from_email' => 'email@yoursite.com',
'mailer_return_path' => null,
'mailer_transport' => 'mail',
'mailer_append_tracking_pixel' => true,
'mailer_convert_embed_images' => false,
'mailer_dsn' => 'smtp://null:25',
'messenger_dsn_email' => 'doctrine://default',
'messenger_retry_strategy_max_retries' => 3,
'messenger_retry_strategy_delay' => 1000,
'messenger_retry_strategy_multiplier' => 2,
'messenger_retry_strategy_max_delay' => 0,
'unsubscribe_text' => null,
'webview_text' => null,
'unsubscribe_message' => null,
'resubscribe_message' => null,
'monitored_email' => [
'general' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
],
'EmailBundle_bounces' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => null,
],
'EmailBundle_unsubscribes' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => null,
],
'EmailBundle_replies' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => null,
],
],
'mailer_is_owner' => false,
'default_signature_text' => null,
'email_frequency_number' => null,
'email_frequency_time' => null,
'show_contact_preferences' => false,
'show_contact_frequency' => false,
'show_contact_pause_dates' => false,
'show_contact_preferred_channels' => false,
'show_contact_categories' => false,
'show_contact_segments' => false,
'mailer_mailjet_sandbox' => false,
'mailer_mailjet_sandbox_default_mail' => null,
'disable_trackable_urls' => false,
],
],
];
#[\PHPUnit\Framework\Attributes\TestDox('Test that the restricted fields are removed from the config')]
public function testRestrictedFieldsAreRemoved(): void
{
$form = $this->factory->create(ConfigType::class, $this->forms);
$this->assertTrue($form->has('emailconfig'));
$emailConfig = $form->get('emailconfig');
// monitored_email is partially restricted so should be included
$this->assertTrue($emailConfig->has('monitored_email'));
$monitoredEmail = $emailConfig->get('monitored_email');
// EmailBundle_bounces is restricted in entirety and thus should not be included
$this->assertFalse($monitoredEmail->has('EmailBundle_bounces'));
// EmailBundle_unsubscribes is partially restricted so should be included
$this->assertTrue($monitoredEmail->has('EmailBundle_unsubscribes'));
$unsubscribes = $monitoredEmail->get('EmailBundle_unsubscribes');
// address under EmailBundle_unsubscribes is restricted so should not be included
$this->assertFalse($unsubscribes->has('address'));
// host under EmailBundle_unsubscribes is not restricted so should be included
$this->assertTrue($unsubscribes->has('host'));
}
#[\PHPUnit\Framework\Attributes\TestDox('Test that the restricted fields are masked')]
public function testRestrictedFieldsAreMasked(): void
{
$this->displayMode = RestrictionHelper::MODE_MASK;
// Rebuild factory to get updated RestrictionHelper
$this->factory = Forms::createFormFactoryBuilder()
->addExtensions($this->getExtensions())
->getFormFactory();
$form = $this->factory->create(ConfigType::class, $this->forms);
/** @var FormInterface<mixed> $address */
$address = $form['emailconfig']['monitored_email']['EmailBundle_unsubscribes']['address'];
$this->assertTrue($address->getConfig()->getOption('attr')['readonly']);
$this->assertTrue($address->getConfig()->getOption('disabled'));
$this->assertEquals(
[
'class' => 'form-control',
'tooltip' => 'mautic.email.config.monitored_email_address.tooltip',
'data-show-on' => '{"config_emailconfig_monitored_email_EmailBundle_unsubscribes_override_settings_1": "checked"}',
'placeholder' => 'mautic.config.restricted',
'readonly' => true,
],
$address->getConfig()->getOption('attr')
);
}
/**
* @return array
*/
protected function getExtensions()
{
$translator = $this->createMock(Translator::class);
$translator->method('trans')
->willReturnCallback(
fn ($key) => $key
);
$validator = $this->createMock(ValidatorInterface::class);
$validator
->method('validate')
->willReturn(new ConstraintViolationList());
$validator
->method('getMetadataFor')
->willReturn(new ClassMetadata(Form::class));
$imapHelper = $this->createMock(Mailbox::class);
// Register monitored email listeners
$dispatcher = new EventDispatcher();
$bouncer = $this->createMock(Bounce::class);
$dispatcher->addSubscriber(new ProcessBounceSubscriber($bouncer));
$unsubscriber = $this->createMock(Unsubscribe::class);
$looper = $this->createMock(FeedbackLoop::class);
$dispatcher->addSubscriber(new ProcessUnsubscribeSubscriber($unsubscriber, $looper));
// This is what we're really testing here
$restrictionHelper = new RestrictionHelper($translator, $this->restrictedFields, $this->displayMode);
$escapeTransformer = new EscapeTransformer([]);
return [
// register the type instances with the PreloadedExtension
new PreloadedExtension(
[
new TextType(),
new ChoiceType(),
new YesNoButtonGroupType(),
new PasswordType(),
new StandAloneButtonType(),
new NumberType(),
new FormButtonsType(),
new ButtonGroupType(),
new EmailConfigType($translator),
new DsnType($this->createMock(DsnTransformerFactory::class), $this->createMock(CoreParametersHelper::class)),
new ConfigMonitoredEmailType($dispatcher),
new ConfigMonitoredMailboxesType($imapHelper),
new ConfigType($restrictionHelper, $escapeTransformer),
],
[]
),
new ValidatorExtension($validator),
];
}
}

View File

@@ -0,0 +1,249 @@
<?php
namespace Mautic\ConfigBundle\Tests\Mapper;
use Mautic\ConfigBundle\Exception\BadFormConfigException;
use Mautic\ConfigBundle\Mapper\ConfigMapper;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
#[\PHPUnit\Framework\Attributes\CoversClass(BadFormConfigException::class)]
#[\PHPUnit\Framework\Attributes\CoversClass(ConfigMapper::class)]
class ConfigMapperTest extends \PHPUnit\Framework\TestCase
{
private $forms = [
'emailconfig' => [
'bundle' => 'EmailBundle',
'formAlias' => 'emailconfig',
'formTheme' => 'MauticEmailBundle:FormTheme\\Config',
'parameters' => [
'mailer_from_name' => 'Mautic',
'mailer_from_email' => 'email@yoursite.com',
'mailer_return_path' => null,
'mailer_transport' => 'mail',
'mailer_append_tracking_pixel' => true,
'mailer_convert_embed_images' => false,
'mailer_dsn' => 'smtp://null:25',
'messenger_dsn_email' => 'doctrine://default',
'messenger_retry_strategy_max_retries' => 3,
'messenger_retry_strategy_delay' => 1000,
'messenger_retry_strategy_multiplier' => 2,
'messenger_retry_strategy_max_delay' => 0,
'unsubscribe_text' => null,
'webview_text' => null,
'unsubscribe_message' => null,
'resubscribe_message' => null,
'monitored_email' => [
'general' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
],
'EmailBundle_bounces' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => null,
],
'EmailBundle_unsubscribes' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => null,
],
'EmailBundle_replies' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => null,
],
],
'mailer_is_owner' => false,
'default_signature_text' => null,
'email_frequency_number' => null,
'email_frequency_time' => null,
'show_contact_preferences' => false,
'show_contact_frequency' => false,
'show_contact_pause_dates' => false,
'show_contact_preferred_channels' => false,
'show_contact_categories' => false,
'show_contact_segments' => false,
'mailer_mailjet_sandbox' => false,
'mailer_mailjet_sandbox_default_mail' => null,
'disable_trackable_urls' => false,
],
],
];
private $config = [
'db_host' => 'dbhost',
'db_user' => 'dbuser',
'monitored_email' => [
'general' => [
'address' => 'test@test.com',
'host' => 'test.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test@test.com',
'password' => 'password',
],
'EmailBundle_bounces' => [
'address' => 'test2@test.com',
'host' => 'test2.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test2@test.com',
'password' => 'password',
'override_settings' => 1,
'folder' => 'INBOX',
],
'EmailBundle_unsubscribes' => [
'address' => 'test3@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
'EmailBundle_replies' => [
'address' => 'test4@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
],
];
#[\PHPUnit\Framework\Attributes\TestDox('Exception should be thrown if parameters key is not found in a form config')]
public function testExceptionIsThrownOnBadFormConfig(): void
{
$this->expectException(BadFormConfigException::class);
$forms = [
'emailconfig' => [
'bundle' => 'EmailBundle',
'formAlias' => 'emailconfig',
'formTheme' => 'MauticEmailBundle:FormTheme\Config',
],
];
$parameterHelper = $this->createMock(CoreParametersHelper::class);
$mapper = new ConfigMapper($parameterHelper, []);
$mapper->bindFormConfigsWithRealValues($forms);
}
#[\PHPUnit\Framework\Attributes\TestDox('Defaults should be bound when local config has no values')]
public function testParametersAreBoundToDefaults(): void
{
$parameterHelper = $this->createMock(CoreParametersHelper::class);
$mapper = new ConfigMapper($parameterHelper, []);
$processedForms = $mapper->bindFormConfigsWithRealValues($this->forms);
$this->assertEquals($this->forms, $processedForms);
}
#[\PHPUnit\Framework\Attributes\TestDox('Defaults should be merged with local config values')]
public function testParametersAreBoundToDefaultsWithLocalConfig(): void
{
$parameterHelper = $this->createMock(CoreParametersHelper::class);
$parameterHelper->method('get')
->willReturnCallback(
fn ($param, $defaultValue) => array_key_exists($param, $this->config) ? $this->config[$param] : $defaultValue
);
$mapper = new ConfigMapper($parameterHelper, []);
$forms = $this->forms;
$processedForms = $mapper->bindFormConfigsWithRealValues($forms);
// Update expected
$forms['emailconfig']['parameters']['monitored_email'] = [
'general' => [
'address' => 'test@test.com',
'host' => 'test.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test@test.com',
'password' => 'password',
],
'EmailBundle_bounces' => [
'address' => 'test2@test.com',
'host' => 'test2.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test2@test.com',
'password' => 'password',
'override_settings' => 1,
'folder' => 'INBOX',
],
'EmailBundle_unsubscribes' => [
'address' => 'test3@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
'EmailBundle_replies' => [
'address' => 'test4@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
];
$this->assertEquals($forms, $processedForms);
}
#[\PHPUnit\Framework\Attributes\TestDox('Defaults should be merged with local config values but restricted fields should be removed')]
public function testParametersAreBoundToDefaultsWithLocalConfigAndRestrictionsAppied(): void
{
$parameterHelper = $this->createMock(CoreParametersHelper::class);
$parameterHelper->method('get')
->willReturnCallback(
fn ($param, $defaultValue) => array_key_exists($param, $this->config) ? $this->config[$param] : $defaultValue
);
$mapper = new ConfigMapper($parameterHelper, ['monitored_email']);
$forms = $this->forms;
$processedForms = $mapper->bindFormConfigsWithRealValues($forms);
// Expected should have had monitored_email unset due to it being restricted
unset($forms['emailconfig']['parameters']['monitored_email']);
$this->assertEquals($forms, $processedForms);
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Mautic\ConfigBundle\Tests\Mapper\Helper;
use Mautic\ConfigBundle\Mapper\Helper\ConfigHelper;
#[\PHPUnit\Framework\Attributes\CoversClass(ConfigHelper::class)]
class ConfigHelperTest extends \PHPUnit\Framework\TestCase
{
#[\PHPUnit\Framework\Attributes\TestDox('Ensure a mixed numeric/string keyed array is formatted to all string based keys')]
public function testNestedLocalParametersAreBoundCorrectly(): void
{
$defaults = [
'db_host' => null,
'db_user' => null,
'api_enabled' => 1,
'monitored_email' => [
'general' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
],
'EmailBundle_bounces' => [
'address' => 'test2@test.com',
'host' => 'test2.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test2@test.com',
'password' => 'password',
'override_settings' => 1,
'folder' => 'INBOX',
],
'EmailBundle_unsubscribes' => [
'address' => 'test3@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
],
];
$config = [
'db_host' => 'dbhost',
'db_user' => 'dbuser',
'monitored_email' => [
'general' => [
'address' => 'test@test.com',
'host' => 'test.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test@test.com',
'password' => 'password',
],
'EmailBundle_bounces' => null,
'EmailBundle_unsubscribes' => [
'address' => 'test3@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
'EmailBundle_replies' => [
'address' => 'test4@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
],
];
$expected = [
// from config
'db_host' => 'dbhost',
'db_user' => 'dbuser',
// from defaults
'api_enabled' => 1,
'monitored_email' => [
// from config
'general' => [
'address' => 'test@test.com',
'host' => 'test.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test@test.com',
'password' => 'password',
],
'EmailBundle_bounces' => [
// from defaults
'address' => 'test2@test.com',
'host' => 'test2.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test2@test.com',
'password' => 'password',
'override_settings' => 1,
'folder' => 'INBOX',
],
// from config
'EmailBundle_unsubscribes' => [
'address' => 'test3@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
// from config
'EmailBundle_replies' => [
'address' => 'test4@test.com',
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
],
];
$this->assertEquals($expected, ConfigHelper::bindNestedConfigValues($config, $defaults));
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Mautic\ConfigBundle\Tests\Mapper\Helper;
use Mautic\ConfigBundle\Mapper\Helper\RestrictionHelper;
#[\PHPUnit\Framework\Attributes\CoversClass(RestrictionHelper::class)]
class RestrictionHelperTest extends \PHPUnit\Framework\TestCase
{
/**
* @var array
*/
private $restrictedFields = [
'db_host',
'db_user',
'monitored_email' => [
'EmailBundle_bounces',
'EmailBundle_unsubscribes' => [
'address',
],
],
];
#[\PHPUnit\Framework\Attributes\TestDox('Ensure a mixed numeric/string keyed array is formatted to all string based keys')]
public function testRestrictedConfigArrayIsFormattedCorrectly(): void
{
$expected = [
'db_host' => 'db_host',
'db_user' => 'db_user',
'monitored_email' => [
'EmailBundle_bounces' => 'EmailBundle_bounces',
'EmailBundle_unsubscribes' => [
'address' => 'address',
],
],
];
$this->assertEquals($expected, RestrictionHelper::prepareRestrictions($this->restrictedFields));
}
#[\PHPUnit\Framework\Attributes\TestDox('Ensure a restrictions are recursively applied')]
public function testApplyingRestrictionsToConfigArray(): void
{
$config = [
'db_host' => 'dbhost',
'db_user' => 'dbuser',
'api_enabled' => 1,
'monitored_email' => [
'general' => [
'address' => 'test@test.com',
'host' => 'test.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test@test.com',
'password' => 'password',
],
'EmailBundle_bounces' => [
'address' => '',
'host' => '',
'port' => '993',
'encryption' => '/ssl',
'user' => '',
'password' => '',
'override_settings' => 0,
'folder' => 'INBOX',
],
'EmailBundle_unsubscribes' => [
'address' => null,
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
],
];
$expected = [
'api_enabled' => 1,
'monitored_email' => [
'general' => [
'address' => 'test@test.com',
'host' => 'test.com',
'port' => '143',
'encryption' => '/tls/novalidate-cert',
'user' => 'test@test.com',
'password' => 'password',
],
'EmailBundle_unsubscribes' => [
'host' => null,
'port' => '993',
'encryption' => '/ssl',
'user' => null,
'password' => null,
'override_settings' => 0,
'folder' => 'INBOX',
],
],
];
$restrictedFields = RestrictionHelper::prepareRestrictions($this->restrictedFields);
$this->assertEquals($expected, RestrictionHelper::applyRestrictions($config, $restrictedFields));
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Mautic\ConfigBundle\Tests\Service;
use Mautic\ConfigBundle\Service\ConfigChangeLogger;
use Mautic\CoreBundle\Helper\IpLookupHelper;
use Mautic\CoreBundle\Model\AuditLogModel;
class ConfigChangeLoggerTest extends \PHPUnit\Framework\TestCase
{
public function testSetOriginalNormData(): void
{
$ipLookupHelper = $this->createMock(IpLookupHelper::class);
$auditLogModel = $this->createMock(AuditLogModel::class);
$logger = new ConfigChangeLogger($ipLookupHelper, $auditLogModel);
$this->assertEquals($logger, $logger->setOriginalNormData([]));
}
public function testOriginalNormDataExpected(): void
{
$this->expectException(\RuntimeException::class);
$ipLookupHelper = $this->createMock(IpLookupHelper::class);
$ipLookupHelper->expects($this->never())->method('getIpAddressFromRequest');
$auditLogModel = $this->createMock(AuditLogModel::class);
$auditLogModel->expects($this->never())->method('writeToLog');
$logger = new ConfigChangeLogger($ipLookupHelper, $auditLogModel);
$logger->log([]);
}
public function testNothingToLog(): void
{
$ipLookupHelper = $this->createMock(IpLookupHelper::class);
$ipLookupHelper->expects($this->never())->method('getIpAddressFromRequest');
$auditLogModel = $this->createMock(AuditLogModel::class);
$auditLogModel->expects($this->never())->method('writeToLog');
$logger = new ConfigChangeLogger($ipLookupHelper, $auditLogModel);
$originalData = $postData = [
'bundle' => [
'key' => 'value',
],
];
$this->assertEquals($logger, $logger->setOriginalNormData($originalData));
$logger->log($postData);
}
public function testLog(): void
{
$change = [
'key2' => 'changedValue',
];
$filterMe = [
'transifex_password' => 'dhjsakjfda',
'mailer_is_owner' => 'lksajhd',
];
$log = [
'bundle' => 'config',
'object' => 'config',
'objectId' => 0,
'action' => 'update',
'details' => $change,
'ipAddress' => null,
];
$ipLookupHelper = $this->createMock(IpLookupHelper::class);
$ipLookupHelper->expects($this->once())->method('getIpAddressFromRequest');
$auditLogModel = $this->createMock(AuditLogModel::class);
$auditLogModel->expects($this->once())->method('writeToLog')->with($log);
$logger = new ConfigChangeLogger($ipLookupHelper, $auditLogModel);
$originalData = [
'bundle' => [
'key' => 'value',
],
'bundle2' => [
'parameters' => [
'key2' => 'value2',
],
],
];
$postData = [
'bundle' => [
'key' => 'value',
],
'bundle2' => array_merge($change, $filterMe),
];
$this->assertEquals($logger, $logger->setOriginalNormData($originalData));
$logger->log($postData);
}
}