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,171 @@
<?php
namespace Mautic\PluginBundle\Tests;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Cache\ResultCacheOptions;
use Mautic\CoreBundle\Helper\BundleHelper;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\CoreBundle\Helper\PathsHelper;
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
use Mautic\PluginBundle\Entity\IntegrationRepository;
use Mautic\PluginBundle\Entity\Plugin;
use Mautic\PluginBundle\Entity\PluginRepository;
use Mautic\PluginBundle\Event\PluginIntegrationKeyEvent;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use Mautic\PluginBundle\Model\PluginModel;
use Mautic\PluginBundle\PluginEvents;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
use Twig\Environment;
class ConfigFormTest extends KernelTestCase
{
protected function setUp(): void
{
self::bootKernel();
}
public function testConfigForm(): void
{
$plugins = $this->getIntegrationObject()->getIntegrationObjects();
foreach ($plugins as $name => $s) {
$featureSettings = $s->getFormSettings();
$this->assertArrayHasKey('requires_callback', $featureSettings);
$this->assertArrayHasKey('requires_authorization', $featureSettings);
if ($featureSettings['requires_callback']) {
$this->assertNotEmpty($s->getAuthCallbackUrl());
}
}
}
public function testOauth(): void
{
$connectWiseHeader = ['appcookie' => 'rookie'];
self::getContainer()->get('event_dispatcher')->addListener(
PluginEvents::PLUGIN_ON_INTEGRATION_KEYS_DECRYPT,
function (PluginIntegrationKeyEvent $event) use ($connectWiseHeader): PluginIntegrationKeyEvent {
$event->setKeys($connectWiseHeader);
return $event;
}
);
$plugins = $this->getIntegrationObject()->getIntegrationObjects();
$url = 'https://test.com';
$parameters = ['a' => 'testa', 'b' => 'testb'];
$method = 'GET';
$authType = 'oauth2';
$expected = [];
$expected['Connectwise'] = $this->getOauthData('', ['clientId' => $connectWiseHeader['appcookie']]);
$expected['OneSignal'] = $this->getOauthData('');
$expected['Twilio'] = $this->getOauthData('');
$expected['Vtiger'] = $this->getOauthData('sessionName');
$expected['Dynamics'] = $this->getOauthData('access_token');
$expected['Salesforce'] = $this->getOauthData('access_token');
$expected['Sugarcrm'] = $this->getOauthData('access_token');
$expected['Zoho'] = $this->getOauthData('access_token');
$expected['Hubspot'] = $this->getOauthData('hapikey');
foreach ($plugins as $index => $integration) {
$this->assertSame($expected[$index], $integration->prepareRequest($url, $parameters, $method, ['appcookie' => 'ololo'], $authType));
}
}
/**
* @param array<string> $headers
*
* @return array<mixed>
*/
private function getOauthData(string $key, array $headers = []): array
{
$result = [
[
'a' => 'testa',
'b' => 'testb',
$key => '',
], [
'oauth-token: '.$key,
'Authorization: OAuth ',
],
];
if ([] !== $headers) {
$result[1] = array_merge($result[1], $headers);
}
return $result;
}
public function testAmendLeadDataBeforeMauticPopulate(): void
{
$plugins = $this->getIntegrationObject()->getIntegrationObjects();
$object = 'company';
$data = ['company_name' => 'company_name', 'email' => 'company_email'];
foreach ($plugins as $integration) {
$methodExists = method_exists($integration, 'amendLeadDataBeforeMauticPopulate');
if ($methodExists) {
$count = $integration->amendLeadDataBeforeMauticPopulate($data, $object);
$this->assertGreaterThanOrEqual(0, $count);
}
}
}
public function getIntegrationObject()
{
// create an integration object
$pathsHelper = $this->createMock(PathsHelper::class);
$bundleHelper = $this->createMock(BundleHelper::class);
$pluginModel = $this->createMock(PluginModel::class);
$coreParametersHelper = new CoreParametersHelper(self::$kernel->getContainer());
$twig = $this->createMock(Environment::class);
$entityManager = $this->createMock(EntityManager::class);
$pluginRepository = $this->createMock(PluginRepository::class);
$registeredPluginBundles = static::getContainer()->getParameter('mautic.plugin.bundles');
$mauticPlugins = static::getContainer()->getParameter('mautic.bundles');
$bundleHelper->method('getPluginBundles')->willReturn($registeredPluginBundles);
$bundleHelper->method('getMauticBundles')->willReturn(array_merge($mauticPlugins, $registeredPluginBundles));
$integrationEntityRepository = $this->createMock(IntegrationEntityRepository::class);
$integrationRepository = $this->createMock(IntegrationRepository::class);
$entityManager
->method('getRepository')
->willReturnMap(
[
[Plugin::class, $pluginRepository],
[\Mautic\PluginBundle\Entity\Integration::class, $integrationRepository],
[\Mautic\PluginBundle\Entity\IntegrationEntity::class, $integrationEntityRepository],
]
);
$pluginModel->method('getEntities')
->with(
[
'hydration_mode' => 'hydrate_array',
'index' => 'bundle',
'result_cache' => new ResultCacheOptions(Plugin::CACHE_NAMESPACE),
]
)->willReturn([
'MauticCrmBundle' => ['id' => 1],
]);
$integrationHelper = new IntegrationHelper(
self::getContainer(),
$entityManager,
$pathsHelper,
$bundleHelper,
$coreParametersHelper,
$twig,
$pluginModel
);
return $integrationHelper;
}
}

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Controller;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use PHPUnit\Framework\Assert;
use Symfony\Component\HttpFoundation\Request;
class PluginControllerTest extends MauticMysqlTestCase
{
public function testConfigurePluginSuccessValidation(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/s/plugins/config/Twilio');
$form = $crawler->filter('form')->form();
$form->setValues([
'integration_details' => [
'isPublished' => 0,
'apiKeys' => [
'username' => 'valid_username',
'password' => 'valid_password',
],
],
]);
$this->client->submit($form);
Assert::assertTrue($this->client->getResponse()->isOk());
}
public function testConfigurePluginValidationError(): void
{
$crawler = $this->client->request(Request::METHOD_GET, '/s/plugins/config/Twilio');
$form = $crawler->filter('form')->form();
$form->setValues([
'integration_details' => [
'isPublished' => 1,
'apiKeys' => [
'username' => '',
'password' => 'bbb',
],
],
]);
$crawler = $this->client->submit($form);
Assert::assertStringContainsString('A value is required.', $crawler->filter('#integration_details_apiKeys div')->html());
}
}

View File

@@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\DependencyInjection\Compiler;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use Mautic\PluginBundle\Tests\Integration\ClientFactory;
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
class TestPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container): void
{
$container->register(ClientFactory::class, ClientFactory::class)
->setArguments([new Reference('mautic.http.client')]);
foreach ($container->getDefinitions() as $definition) {
$class = (string) $definition->getClass();
/** @phpstan-ignore-next-line Ignore as AbstractIntegration is deprecated */
if (str_starts_with($class, 'Mautic') && is_subclass_of($class, AbstractIntegration::class)) {
$definition->addMethodCall('setClientFactory', [new Reference(ClientFactory::class)]);
}
}
}
}

View File

@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Entity;
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
use PHPUnit\Framework\Assert;
/**
* IntegrationRepository.
*/
class IntegrationEntityRepositoryTest extends MauticMysqlTestCase
{
/**
* @var string
*/
private $prefix;
/**
* @var IntegrationEntityRepository
*/
private $integrationEntityRepository;
protected function setUp(): void
{
parent::setUp();
$this->prefix = static::getContainer()->getParameter('mautic.db_table_prefix');
$this->integrationEntityRepository = $this->em->getRepository(\Mautic\PluginBundle\Entity\IntegrationEntity::class);
}
public function testThatGetIntegrationsEntityIdReturnsCorrectValues(): void
{
$now = new \DateTimeImmutable();
$integrationEntityId = random_int(1, 1000);
$internalEntityId = random_int(1, 1000);
$this->connection->insert($this->prefix.'integration_entity', [
'date_added' => $now->format('Y-m-d H:i:s'),
'integration' => 'someIntegration',
'integration_entity' => 'someIntegrationEntity',
'integration_entity_id' => $integrationEntityId,
'internal_entity' => 'someInternalEntity',
'internal_entity_id' => $internalEntityId,
'last_sync_date' => null,
'internal' => 'someInternalValue',
]);
$results = $this->integrationEntityRepository->getIntegrationsEntityId(
'someIntegration',
'someIntegrationEntity',
'someInternalEntity',
[$internalEntityId],
null,
null,
false,
0,
0,
null
);
Assert::assertCount(1, $results);
Assert::assertSame($integrationEntityId, (int) $results[0]['integration_entity_id']);
Assert::assertSame($internalEntityId, (int) $results[0]['internal_entity_id']);
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Mautic\PluginBundle\Tests\Entity;
use Mautic\PluginBundle\Entity\Plugin;
class PluginTest extends \PHPUnit\Framework\TestCase
{
public function testEmptyDescription(): void
{
$plugin = new Plugin();
$this->assertNull($plugin->getDescription());
$this->assertNull($plugin->getPrimaryDescription());
$this->assertNull($plugin->getSecondaryDescription());
$this->assertFalse($plugin->hasSecondaryDescription());
}
public function testSimpleDescription(): void
{
$description = 'This is the best plugin in the whole galaxy';
$plugin = new Plugin();
$plugin->setDescription($description);
$this->assertEquals($description, $plugin->getDescription());
$this->assertEquals($description, $plugin->getPrimaryDescription());
$this->assertNull($plugin->getSecondaryDescription());
$this->assertFalse($plugin->hasSecondaryDescription());
}
public function testSecondaryDescriptionWithUnixLineEnding(): void
{
$description = "This is the best plugin in the whole galaxy\n---\nLearn more about it <a href=\"#\">here</a>";
$plugin = new Plugin();
$plugin->setDescription($description);
$this->assertEquals($description, $plugin->getDescription());
$this->assertEquals('This is the best plugin in the whole galaxy', $plugin->getPrimaryDescription());
$this->assertEquals('Learn more about it <a href="#">here</a>', $plugin->getSecondaryDescription());
$this->assertTrue($plugin->hasSecondaryDescription());
}
public function testSecondaryDescriptionWithWinLineEnding(): void
{
$description = "This is the best plugin in the whole galaxy\n\r---\n\rLearn more about it <a href=\"#\">here</a>";
$plugin = new Plugin();
$plugin->setDescription($description);
$this->assertEquals($description, $plugin->getDescription());
$this->assertEquals('This is the best plugin in the whole galaxy', $plugin->getPrimaryDescription());
$this->assertEquals('Learn more about it <a href="#">here</a>', $plugin->getSecondaryDescription());
$this->assertTrue($plugin->hasSecondaryDescription());
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Event;
use Mautic\PluginBundle\Event\PluginIsPublishedEvent;
class PluginIsPublishedEventTest extends \PHPUnit\Framework\TestCase
{
public function testSettersGetters(): void
{
$pluginIsPublishedEvent = new PluginIsPublishedEvent(1, 'testIntegration');
$this->assertSame('testIntegration', $pluginIsPublishedEvent->getIntegrationName());
$this->assertSame(1, $pluginIsPublishedEvent->getValue());
$this->assertSame('', $pluginIsPublishedEvent->getMessage());
$this->assertTrue($pluginIsPublishedEvent->isCanPublish());
$pluginIsPublishedEvent->setMessage('This is test message.');
$this->assertSame('This is test message.', $pluginIsPublishedEvent->getMessage());
$pluginIsPublishedEvent->setCanPublish(false);
$this->assertFalse($pluginIsPublishedEvent->isCanPublish());
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\EventListener;
use Mautic\PluginBundle\Event\PluginIntegrationRequestEvent;
use Mautic\PluginBundle\EventListener\IntegrationSubscriber;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
final class IntegrationSubscriberTest extends TestCase
{
public function testOnRequestLogging(): void
{
$event = $this->createMock(PluginIntegrationRequestEvent::class);
$event->method('getIntegrationName')->willReturn('Integration');
$event->method('getHeaders')->willReturn(['Authorization: Bearer some_token']);
$event->method('getMethod')->willReturn('POST');
$event->method('getUrl')->willReturn('https://mautic.org');
$event->method('getParameters')->willReturn(['key' => 'value']);
$event->method('getSettings')->willReturn(['setting' => 'value']);
$authorization = ['Authorization: Bearer [REDACTED]'];
$authorization = var_export($authorization, true);
$logger = $this->createMock(LoggerInterface::class);
$matcher = $this->exactly(4);
$logger->expects($matcher)
->method('debug')->willReturnCallback(function (...$parameters) use ($matcher, $authorization) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('INTEGRATION REQUEST URL: POST https://mautic.org', $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame("INTEGRATION REQUEST HEADERS: \n".$authorization.PHP_EOL, $parameters[0]);
}
});
$subscriber = new IntegrationSubscriber($logger);
$subscriber->onRequest($event);
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\EventListener;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Event\LeadEvent;
use Mautic\PluginBundle\Entity\Integration;
use Mautic\PluginBundle\Entity\IntegrationEntityRepository;
use Mautic\PluginBundle\Entity\IntegrationRepository;
use Mautic\PluginBundle\EventListener\LeadSubscriber;
use Mautic\PluginBundle\Model\PluginModel;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class LeadSubscriberTest extends TestCase
{
private LeadSubscriber $subscriber;
/**
* @var IntegrationEntityRepository|MockObject
*/
private $integrationEntityRepository;
/**
* @var IntegrationRepository|MockObject
*/
private $integrationRepository;
protected function setUp(): void
{
$pluginModel = $this->createMock(PluginModel::class);
$this->integrationRepository = $this->createMock(IntegrationRepository::class);
$this->integrationEntityRepository = $this->createMock(IntegrationEntityRepository::class);
$this->subscriber = new LeadSubscriber(
$pluginModel,
$this->integrationRepository
);
$pluginModel->expects($this->once())
->method('getIntegrationEntityRepository')
->willReturn($this->integrationEntityRepository);
}
public function testOnLeadSaveWithoutActiveIntegration(): void
{
$this->integrationRepository->expects($this->once())
->method('getIntegrations')
->willReturn([]);
$this->integrationEntityRepository->expects($this->never())
->method('updateErrorLeads');
$this->subscriber->onLeadSave(new LeadEvent(new Lead()));
}
public function testOnLeadSaveWithActiveIntegration(): void
{
$integration = new Integration();
$integration->setIsPublished(true);
$integration->setApiKeys(['key' => 'some']);
$integration->setSupportedFeatures(['push_lead']);
$this->integrationRepository->expects($this->once())
->method('getIntegrations')
->willReturn([$integration]);
$this->integrationEntityRepository->expects($this->once())
->method('updateErrorLeads');
$this->subscriber->onLeadSave(new LeadEvent(new Lead()));
}
}

View File

@@ -0,0 +1,20 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Exception;
use Mautic\PluginBundle\Exception\ApiErrorException;
class ApiErrorExceptionTest extends \PHPUnit\Framework\TestCase
{
public function testShortMessage(): void
{
$apiErrorException = new ApiErrorException('Main Error message.');
$this->assertEmpty($apiErrorException->getShortMessage());
$shortMessage = 'This is short message';
$apiErrorException->setShortMessage($shortMessage);
$this->assertSame($shortMessage, $apiErrorException->getShortMessage());
}
}

View File

@@ -0,0 +1,101 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Form\Constraint;
use Mautic\PluginBundle\Event\PluginIsPublishedEvent;
use Mautic\PluginBundle\Form\Constraint\CanPublish;
use Mautic\PluginBundle\Form\Constraint\CanPublishValidator;
use Mautic\PluginBundle\PluginEvents;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\Context\ExecutionContext;
use Symfony\Component\Validator\Exception\UnexpectedTypeException;
class CanPublishValidatorTest extends TestCase
{
/**
* @var MockObject|EventDispatcherInterface
*/
private $dispatcher;
/**
* @var MockObject|PluginIsPublishedEvent
*/
private $event;
private CanPublishValidator $canPublishValidator;
protected function setUp(): void
{
parent::setUp();
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->event = $this->createMock(PluginIsPublishedEvent::class);
$this->canPublishValidator = new CanPublishValidator($this->dispatcher);
}
public function testValidate(): void
{
$this->event->expects($this->once())
->method('isCanPublish')
->willReturn(false);
$this->event->expects($this->once())
->method('getMessage')
->willReturn('Error in validation');
$this->dispatcher->expects($this->once())
->method('dispatch')
->willReturn($this->event);
$this->canPublishValidator->initialize($this->createMock(ExecutionContext::class));
$this->canPublishValidator->validate(1, new CanPublish('testIntegration'));
}
public function testEventNotDispatchedIfUnpublished(): void
{
$this->event->expects($this->never())
->method('isCanPublish')
->willReturn(false);
$this->event->expects($this->never())
->method('getMessage')
->willReturn('Error in validation');
$this->dispatcher->expects($this->never())
->method('dispatch')
->with(PluginEvents::PLUGIN_IS_PUBLISHED_STATE_CHANGING)
->willReturn($this->event);
$this->canPublishValidator->initialize($this->createMock(ExecutionContext::class));
$this->canPublishValidator->validate(0, new CanPublish('testIntegration'));
}
public function testExceptionIsThrown(): void
{
$this->event->expects($this->never())
->method('isCanPublish')
->willReturn(false);
$this->event->expects($this->never())
->method('getMessage')
->willReturn('Error in validation');
$this->dispatcher->expects($this->never())
->method('dispatch')
->with(PluginEvents::PLUGIN_IS_PUBLISHED_STATE_CHANGING)
->willReturn($this->event);
$this->canPublishValidator->initialize($this->createMock(ExecutionContext::class));
$this->expectException(UnexpectedTypeException::class);
$this->canPublishValidator->validate(1, new class extends Constraint {});
}
}

View File

@@ -0,0 +1,316 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Form\Type;
use Mautic\CoreBundle\Form\Type\StandAloneButtonType;
use Mautic\PluginBundle\Entity\Integration;
use Mautic\PluginBundle\Form\Type\DetailsType;
use Mautic\PluginBundle\Form\Type\KeysType;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
class DetailsTypeTest extends TestCase
{
public function testBuildFormRemovesHiddenKeys(): void
{
/** @var MockObject&FormBuilderInterface $builder */
$builder = $this->createMock(FormBuilderInterface::class);
$options = ['integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
$integrationObject = $this->createMock(AbstractIntegration::class);
$integrationObject->expects(self::once())
->method('getFormDisplaySettings')
->willReturn(['hide_keys' => ['key1', 'key3']]);
$integrationObject->expects(self::once())
->method('getRequiredKeyFields')
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
$integrationObject->expects(self::once())
->method('decryptApiKeys')
->willReturn([]);
$integrationObject->expects(self::never())
->method('isAuthorized');
$integrationObject->expects(self::once())
->method('getSupportedFeatures')
->willReturn([]);
$integration = $this->createMock(Integration::class);
$integration->method('getApiKeys')
->willReturn([]);
$integration->expects(self::never())
->method('getId');
$integration->expects(self::never())
->method('getSupportedFeatures');
$options['integration_object'] = $integrationObject;
$options['data'] = $integration;
$calls = 0;
$builder->expects(self::never())
->method('setAction');
$builder->expects(self::atLeastOnce())
->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use (&$calls, $builder): FormBuilderInterface {
if ('apiKeys' === $key) {
++$calls;
self::assertSame(KeysType::class, $fieldFQCN);
self::assertArrayHasKey('integration_keys', $options);
self::assertSame(['key2' => 'value2', 'key4' => 'value4'], $options['integration_keys']);
}
if ('authButton' === $key) {
++$calls;
}
if ('supportedFeatures' === $key) {
++$calls;
}
return $builder;
});
$integrationObject->expects(self::once())
->method('modifyForm')
->with($builder, $options);
$form = new DetailsType();
$form->buildForm($builder, $options);
self::assertSame(1, $calls);
}
#[\PHPUnit\Framework\Attributes\DataProvider('authorizedDataProvider')]
public function testBuildFormRequiresAuthorization(bool $isAuthorized, string $label): void
{
/** @var MockObject&FormBuilderInterface $builder */
$builder = $this->createMock(FormBuilderInterface::class);
$options = ['integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
$integrationObject = $this->createMock(AbstractIntegration::class);
$integrationObject->expects(self::once())
->method('getFormDisplaySettings')
->willReturn(['hide_keys' => ['key3'], 'requires_authorization' => true]);
$integrationObject->expects(self::once())
->method('getRequiredKeyFields')
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
$integrationObject->expects(self::once())
->method('decryptApiKeys')
->willReturn(['decrypted']);
$integrationObject->expects(self::once())
->method('isAuthorized')
->willReturn($isAuthorized);
$integrationObject->expects(self::once())
->method('getSupportedFeatures')
->willReturn([]);
$integration = $this->createMock(Integration::class);
$integration->method('getApiKeys')
->willReturn([]);
$integration->expects(self::never())
->method('getId');
$integration->expects(self::never())
->method('getSupportedFeatures');
$options['integration_object'] = $integrationObject;
$options['data'] = $integration;
$calls = 0;
$builder->expects(self::never())
->method('setAction');
$builder->expects(self::atLeastOnce())
->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($label, &$calls, $builder): FormBuilderInterface {
if ('apiKeys' === $key) {
++$calls;
self::assertSame(KeysType::class, $fieldFQCN);
self::assertArrayHasKey('integration_keys', $options);
self::assertSame(['key1' => 'value1', 'key2' => 'value2', 'key4' => 'value4'], $options['integration_keys']);
}
if ('authButton' === $key) {
++$calls;
self::assertSame(StandAloneButtonType::class, $fieldFQCN);
self::assertArrayHasKey('label', $options);
self::assertSame('mautic.integration.form.'.$label, $options['label']);
}
if ('supportedFeatures' === $key) {
++$calls;
}
return $builder;
});
$integrationObject->expects(self::once())
->method('modifyForm')
->with($builder, $options);
$form = new DetailsType();
$form->buildForm($builder, $options);
self::assertSame(2, $calls);
}
public static function authorizedDataProvider(): \Generator
{
yield 'authorized' => [true, 'reauthorize'];
yield 'not authorized' => [false, 'authorize'];
}
/**
* @param array<string> $expectedFeatures
*/
#[\PHPUnit\Framework\Attributes\DataProvider('withFeaturesProvider')]
public function testBuildFormWithFeatures(?int $integrationId, array $expectedFeatures): void
{
/** @var MockObject&FormBuilderInterface $builder */
$builder = $this->createMock(FormBuilderInterface::class);
$options = ['integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
$integrationObject = $this->createMock(AbstractIntegration::class);
$integrationObject->expects(self::once())
->method('getFormDisplaySettings')
->willReturn(['hide_keys' => ['key1']]);
$integrationObject->expects(self::once())
->method('getRequiredKeyFields')
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
$integrationObject->expects(self::once())
->method('decryptApiKeys')
->willReturn(['decrypted']);
$integrationObject->expects(self::never())
->method('isAuthorized');
$integrationObject->expects(self::once())
->method('getSupportedFeatures')
->willReturn(['non-configured']);
$integration = $this->createMock(Integration::class);
$integration->method('getApiKeys')
->willReturn([]);
$integration->expects(self::once())
->method('getId')
->willReturn($integrationId);
$integration->expects(self::once())
->method('getSupportedFeatures')
->willReturn(['configured']);
$options['integration_object'] = $integrationObject;
$options['data'] = $integration;
$calls = 0;
$builder->expects(self::never())
->method('setAction');
$builder->expects(self::atLeastOnce())
->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($expectedFeatures, &$calls, $builder): FormBuilderInterface {
if ('apiKeys' === $key) {
++$calls;
self::assertSame(KeysType::class, $fieldFQCN);
self::assertArrayHasKey('integration_keys', $options);
self::assertSame(['key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4'], $options['integration_keys']);
}
if ('supportedFeatures' === $key) {
++$calls;
self::assertSame(ChoiceType::class, $fieldFQCN);
self::assertArrayHasKey('choices', $options);
self::assertSame(['mautic.integration.form.feature.non-configured' => 'non-configured'], $options['choices']);
self::assertArrayHasKey('data', $options);
self::assertSame($expectedFeatures, $options['data']);
}
if ('authButton' === $key) {
++$calls;
}
return $builder;
});
$integrationObject->expects(self::once())
->method('modifyForm')
->with($builder, $options);
$form = new DetailsType();
$form->buildForm($builder, $options);
self::assertSame(2, $calls);
}
public static function withFeaturesProvider(): \Generator
{
yield 'create integration' => [null, ['non-configured']];
yield 'edit integration' => [1, ['configured']];
}
public function testBuildFormWithAction(): void
{
$action = 'the_action';
$options = ['action' => $action, 'integration' => 'integration', 'lead_fields' => 'lead_fields', 'company_fields' => 'company_fields'];
/** @var MockObject&FormBuilderInterface $builder */
$builder = $this->createMock(FormBuilderInterface::class);
$integrationObject = $this->createMock(AbstractIntegration::class);
$integrationObject->expects(self::once())
->method('getFormDisplaySettings')
->willReturn([]);
$integrationObject->expects(self::once())
->method('getRequiredKeyFields')
->willReturn(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4']);
$integrationObject->expects(self::once())
->method('decryptApiKeys')
->willReturn(['decrypted']);
$integrationObject->expects(self::never())
->method('isAuthorized');
$integrationObject->expects(self::once())
->method('getSupportedFeatures');
$integration = $this->createMock(Integration::class);
$integration->method('getApiKeys')
->willReturn([]);
$integration->expects(self::never())
->method('getId');
$integration->expects(self::never())
->method('getSupportedFeatures');
$options['integration_object'] = $integrationObject;
$options['data'] = $integration;
$calls = 0;
$builder->expects(self::once())
->method('setAction')
->with($action);
$builder->expects(self::atLeastOnce())
->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use (&$calls, $builder): FormBuilderInterface {
if ('apiKeys' === $key) {
++$calls;
self::assertSame(KeysType::class, $fieldFQCN);
self::assertArrayHasKey('integration_keys', $options);
self::assertSame(['key1' => 'value1', 'key2' => 'value2', 'key3' => 'value3', 'key4' => 'value4'], $options['integration_keys']);
}
if ('supportedFeatures' === $key) {
++$calls;
}
if ('authButton' === $key) {
++$calls;
}
return $builder;
});
$integrationObject->expects(self::once())
->method('modifyForm')
->with($builder, $options);
$form = new DetailsType();
$form->buildForm($builder, $options);
self::assertSame(1, $calls);
}
}

View File

@@ -0,0 +1,286 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Form\Type;
use Mautic\PluginBundle\Entity\Integration;
use Mautic\PluginBundle\Entity\Plugin;
use Mautic\PluginBundle\Form\Type\IntegrationCampaignsType;
use Mautic\PluginBundle\Form\Type\IntegrationConfigType;
use Mautic\PluginBundle\Form\Type\IntegrationsListType;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormInterface;
class IntegrationsListTypeTest extends TestCase
{
public function testDataDoesNotHaveIntegration(): void
{
$pluginName = 'plugin name';
$integration1 = $this->createMock(Integration::class);
$integration1->expects(self::once())
->method('isPublished')
->willReturn(false);
$integration1->expects(self::never())
->method('getPlugin');
$plugin = $this->createMock(Plugin::class);
$plugin->expects(self::once())
->method('getName')
->willReturn($pluginName);
$integration2 = $this->createMock(Integration::class);
$integration2->expects(self::once())
->method('isPublished')
->willReturn(true);
$integration2->expects(self::once())
->method('getPlugin')
->willReturn($plugin);
$integrationInstance1 = $this->createMock(AbstractIntegration::class);
$integrationInstance1->expects(self::once())
->method('getIntegrationSettings')
->willReturn($integration1);
$integrationInstance2 = $this->createMock(AbstractIntegration::class);
$integrationInstance2->expects(self::once())
->method('getIntegrationSettings')
->willReturn($integration2);
$integrationInstance2->expects(self::once())
->method('getDisplayName')
->willReturn('Integration 2');
$integrationInstance2->expects(self::once())
->method('getName')
->willReturn('integration-2');
$integrationHelper = $this->createMock(IntegrationHelper::class);
$integrationHelper->expects(self::once())
->method('getIntegrationObjects')
->with(null, 'features', true)
->willReturn(['integration1' => $integrationInstance1, 'integration2' => $integrationInstance2]);
$integrationHelper->method('getIntegrationObject')
->willReturn($this->createMock(AbstractIntegration::class));
$callsForm = 0;
/** @var MockObject&FormInterface $form */
$form = $this->createMock(FormInterface::class);
$form->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use (&$callsForm, $form): FormInterface {
if ('config' === $key) {
++$callsForm;
self::assertSame(IntegrationConfigType::class, $fieldFQCN);
self::assertArrayHasKey('integration', $options);
self::assertNull($options['integration']);
self::assertArrayHasKey('data', $options);
self::assertSame([], $options['data']);
}
if ('campaign_member_status' === $key) {
++$callsForm;
self::assertSame(IntegrationCampaignsType::class, $fieldFQCN);
self::assertArrayHasKey('attr', $options);
self::assertSame('integration-campaigns-status hide', $options['attr']['class']);
self::assertArrayHasKey('data', $options);
self::assertSame([], $options['data']);
}
return $form;
});
$data = [];
$formEvent = $this->createMock(FormEvent::class);
$formEvent->expects(self::once())
->method('getForm')
->willReturn($form);
$formEvent->expects(self::once())
->method('getData')
->willReturn($data);
/** @var MockObject&FormBuilderInterface $builder */
$builder = $this->createMock(FormBuilderInterface::class);
$callsBuilder = 0;
$builder->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($pluginName, &$callsBuilder, $builder): FormBuilderInterface {
if ('integration' === $key) {
++$callsBuilder;
self::assertSame(ChoiceType::class, $fieldFQCN);
self::assertArrayHasKey('choices', $options);
self::assertSame([
'' => '',
$pluginName => [
'Integration 2' => 'integration-2',
],
], $options['choices']);
}
return $builder;
});
$calledCallback = false;
$builder->expects(self::exactly(2))
->method('addEventListener')
->willReturnCallback(static function (string $eventName, callable $callback) use ($formEvent, &$calledCallback, $builder): FormBuilderInterface {
self::assertContains($eventName, [FormEvents::PRE_SET_DATA, FormEvents::PRE_SUBMIT]);
if (!$calledCallback) {
$calledCallback = true;
$callback($formEvent);
}
return $builder;
});
$integrationsListType = new IntegrationsListType($integrationHelper);
$integrationsListType->buildForm($builder, ['supported_features' => 'features']);
self::assertSame(1, $callsBuilder);
self::assertSame(2, $callsForm);
}
public function testDataHaveIntegration(): void
{
$pluginName = 'plugin name';
$integration1 = $this->createMock(Integration::class);
$integration1->expects(self::once())
->method('isPublished')
->willReturn(false);
$integration1->expects(self::never())
->method('getPlugin');
$plugin = $this->createMock(Plugin::class);
$plugin->expects(self::once())
->method('getName')
->willReturn($pluginName);
$integration2 = $this->createMock(Integration::class);
$integration2->expects(self::once())
->method('isPublished')
->willReturn(true);
$integration2->expects(self::once())
->method('getPlugin')
->willReturn($plugin);
$integrationInstance1 = $this->createMock(AbstractIntegration::class);
$integrationInstance1->expects(self::once())
->method('getIntegrationSettings')
->willReturn($integration1);
$integrationInstance2 = $this->createMock(AbstractIntegration::class);
$integrationInstance2->expects(self::once())
->method('getIntegrationSettings')
->willReturn($integration2);
$integrationInstance2->expects(self::once())
->method('getDisplayName')
->willReturn('Integration 2');
$integrationInstance2->expects(self::once())
->method('getName')
->willReturn('integration-2');
$integrationHelper = $this->createMock(IntegrationHelper::class);
$integrationHelper->expects(self::once())
->method('getIntegrationObjects')
->with(null, 'features', true)
->willReturn(['integration1' => $integrationInstance1, 'integration2' => $integrationInstance2]);
$integrationHelper->method('getIntegrationObject')
->willReturn($this->createMock(AbstractIntegration::class));
$callsForm = 0;
$form = $this->createMock(FormInterface::class);
$form->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($integrationInstance1, &$callsForm, $form): FormInterface {
if ('config' === $key) {
++$callsForm;
self::assertSame(IntegrationConfigType::class, $fieldFQCN);
self::assertArrayHasKey('integration', $options);
self::assertSame($integrationInstance1, $options['integration']);
self::assertArrayHasKey('data', $options);
self::assertSame(['config' => 'test'], $options['data']);
}
if ('campaign_member_status' === $key) {
++$callsForm;
self::assertSame(IntegrationCampaignsType::class, $fieldFQCN);
self::assertArrayHasKey('attr', $options);
self::assertSame('integration-campaigns-status', $options['attr']['class']);
self::assertArrayHasKey('data', $options);
self::assertSame([
'campaign_member_status' => true,
'some' => 'other',
], $options['data']);
}
return $form;
});
$data = [
'integration' => 'integration1',
'config' => [
'config' => 'test',
],
'campaign_member_status' => [
'campaign_member_status' => true,
'some' => 'other',
],
];
$formEvent = $this->createMock(FormEvent::class);
$formEvent->expects(self::exactly(2))
->method('getForm')
->willReturn($form);
$formEvent->expects(self::exactly(2))
->method('getData')
->willReturn($data);
$callsBuilder = 0;
$builder = $this->createMock(FormBuilderInterface::class);
\assert($builder instanceof FormBuilderInterface);
$builder->method('add')
->willReturnCallback(static function (string $key, string $fieldFQCN, array $options) use ($pluginName, &$callsBuilder, $builder): FormBuilderInterface {
if ('integration' === $key) {
++$callsBuilder;
self::assertSame(ChoiceType::class, $fieldFQCN);
self::assertArrayHasKey('choices', $options);
self::assertSame([
'' => '',
$pluginName => [
'Integration 2' => 'integration-2',
],
], $options['choices']);
}
return $builder;
});
$calledCallback = 0;
$builder->expects(self::exactly(2))
->method('addEventListener')
->willReturnCallback(static function (string $eventName, callable $callback) use ($formEvent, &$calledCallback, $builder): FormBuilderInterface {
self::assertContains($eventName, [FormEvents::PRE_SET_DATA, FormEvents::PRE_SUBMIT]);
++$calledCallback;
$callback($formEvent);
return $builder;
});
$integrationsListType = new IntegrationsListType($integrationHelper);
$integrationsListType->buildForm($builder, ['supported_features' => 'features']);
self::assertSame(1, $callsBuilder);
self::assertSame(4, $callsForm, 'Because callback is called twice due to coverage.');
self::assertSame(2, $calledCallback);
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Mautic\PluginBundle\Tests\Helper;
use Doctrine\DBAL\Schema\Schema;
use Mautic\PluginBundle\Entity\Plugin;
use Symfony\Component\HttpKernel\Bundle\Bundle;
/**
* A stub Base Bundle class which implements stub methods for testing purposes.
*/
abstract class PluginBundleBaseStub extends Bundle
{
public static function onPluginInstall(Plugin $plugin, $metadata = null, $installedSchema = null): void
{
}
/**
* Called by PluginController::reloadAction when the addon version does not match what's installed.
*/
public static function onPluginUpdate(Plugin $plugin, $metadata = null, ?Schema $installedSchema = null)
{
}
}

View File

@@ -0,0 +1,175 @@
<?php
namespace Mautic\PluginBundle\Tests\Helper;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\ORM\Mapping\ClassMetadata;
use Mautic\PluginBundle\Entity\Plugin;
use Mautic\PluginBundle\Event\PluginInstallEvent;
use Mautic\PluginBundle\Event\PluginUpdateEvent;
use Mautic\PluginBundle\Helper\ReloadHelper;
use Mautic\PluginBundle\PluginEvents;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ReloadHelperTest extends \PHPUnit\Framework\TestCase
{
private ReloadHelper $helper;
private array $sampleAllPlugins = [];
private array $sampleMetaData = [];
private array $sampleSchemas = [];
/**
* @var MockObject&EventDispatcherInterface
*/
private MockObject $eventDispatcher;
protected function setUp(): void
{
parent::setUp();
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->helper = new ReloadHelper($this->eventDispatcher);
$this->sampleMetaData = [
'MauticPlugin\MauticZapierBundle' => [
'MauticPlugin\MauticZapierBundle\Entity\SomeTest' => $this->createMock(ClassMetadata::class),
],
];
$sampleSchema = $this->createMock(Schema::class);
$sampleSchema->method('getTables')
->willReturn([]);
$this->sampleSchemas = [
'MauticPlugin\MauticZapierBundle' => $sampleSchema,
];
$this->sampleAllPlugins = [
'MauticZapierBundle' => [
'isPlugin' => true,
'base' => 'MauticZapier',
'bundle' => 'MauticZapierBundle',
'namespace' => 'MauticPlugin\MauticZapierBundle',
'symfonyBundleName' => 'MauticZapierBundle',
'bundleClass' => PluginBundleBaseStub::class,
'permissionClasses' => [],
'relative' => 'plugins/MauticZapierBundle',
'directory' => '/Users/jan/dev/mautic/plugins/MauticZapierBundle',
'config' => [
'name' => 'Zapier Integration',
'description' => 'Zapier lets you connect Mautic with 1100+ other apps',
'version' => '1.0',
'author' => 'Mautic',
],
],
];
}
public function testDisableMissingPlugins(): void
{
$sampleInstalledPlugins = [
'MauticZapierBundle' => $this->createSampleZapierPlugin(),
'MauticHappierBundle' => $this->createSampleHappierPlugin(),
];
$disabledPlugins = $this->helper->disableMissingPlugins($this->sampleAllPlugins, $sampleInstalledPlugins);
$this->assertEquals(1, count($disabledPlugins));
$this->assertEquals('Happier Integration', $disabledPlugins['MauticHappierBundle']->getName());
$this->assertTrue($disabledPlugins['MauticHappierBundle']->isMissing());
}
public function testEnableFoundPlugins(): void
{
$zapierPlugin = $this->createSampleZapierPlugin();
$zapierPlugin->setIsMissing(true);
$sampleInstalledPlugins = [
'MauticZapierBundle' => $zapierPlugin,
];
$enabledPlugins = $this->helper->enableFoundPlugins($this->sampleAllPlugins, $sampleInstalledPlugins);
$this->assertEquals(1, count($enabledPlugins));
$this->assertEquals('Zapier Integration', $enabledPlugins['MauticZapierBundle']->getName());
$this->assertFalse($enabledPlugins['MauticZapierBundle']->isMissing());
}
public function testUpdatePlugins(): void
{
$this->sampleAllPlugins['MauticZapierBundle']['config']['version'] = '1.0.1';
$this->sampleAllPlugins['MauticZapierBundle']['config']['description'] = 'Updated description';
$sampleInstalledPlugins = [
'MauticZapierBundle' => $this->createSampleZapierPlugin(),
'MauticHappierBundle' => $this->createSampleHappierPlugin(),
];
$plugin = $this->createSampleZapierPlugin();
$plugin->setVersion('1.0.1');
$plugin->setDescription('Updated description');
$event = new PluginUpdateEvent(
$plugin,
'1.0',
$this->sampleMetaData['MauticPlugin\MauticZapierBundle'],
$this->sampleSchemas['MauticPlugin\MauticZapierBundle']
);
$this->eventDispatcher->expects($this->once())->method('dispatch')->with($event, PluginEvents::ON_PLUGIN_UPDATE);
$updatedPlugins = $this->helper->updatePlugins($this->sampleAllPlugins, $sampleInstalledPlugins, $this->sampleMetaData, $this->sampleSchemas);
$this->assertEquals(1, count($updatedPlugins));
$this->assertEquals('Zapier Integration', $updatedPlugins['MauticZapierBundle']->getName());
$this->assertEquals('1.0.1', $updatedPlugins['MauticZapierBundle']->getVersion());
$this->assertEquals('Updated description', $updatedPlugins['MauticZapierBundle']->getDescription());
}
public function testInstallPlugins(): void
{
$sampleInstalledPlugins = [
'MauticHappierBundle' => $this->createSampleHappierPlugin(),
];
$event = new PluginInstallEvent(
$this->createSampleZapierPlugin(),
$this->sampleMetaData['MauticPlugin\MauticZapierBundle'],
null
);
$this->eventDispatcher->expects($this->once())->method('dispatch')->with($event, PluginEvents::ON_PLUGIN_INSTALL);
$installedPlugins = $this->helper->installPlugins($this->sampleAllPlugins, $sampleInstalledPlugins, $this->sampleMetaData, $this->sampleSchemas);
$this->assertEquals(1, count($installedPlugins));
$this->assertEquals('Zapier Integration', $installedPlugins['MauticZapierBundle']->getName());
$this->assertEquals('1.0', $installedPlugins['MauticZapierBundle']->getVersion());
$this->assertEquals('MauticZapierBundle', $installedPlugins['MauticZapierBundle']->getBundle());
$this->assertEquals('Mautic', $installedPlugins['MauticZapierBundle']->getAuthor());
$this->assertEquals('Zapier lets you connect Mautic with 1100+ other apps', $installedPlugins['MauticZapierBundle']->getDescription());
$this->assertFalse($installedPlugins['MauticZapierBundle']->isMissing());
}
private function createSampleZapierPlugin()
{
$plugin = new Plugin();
$plugin->setName('Zapier Integration');
$plugin->setDescription('Zapier lets you connect Mautic with 1100+ other apps');
$plugin->isMissing(false);
$plugin->setBundle('MauticZapierBundle');
$plugin->setVersion('1.0');
$plugin->setAuthor('Mautic');
return $plugin;
}
private function createSampleHappierPlugin()
{
$plugin = new Plugin();
$plugin->setName('Happier Integration');
$plugin->setDescription('Happier lets you connect Mautic with 1100+ other apps');
$plugin->isMissing(false);
$plugin->setBundle('MauticHappierBundle');
$plugin->setVersion('1.0');
$plugin->setAuthor('Mautic');
return $plugin;
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Helper;
use Mautic\PluginBundle\Helper\oAuthHelper;
use PHPUnit\Framework\TestCase;
final class oAuthHelperTest extends TestCase
{
/**
* @param array<int, string> $headers
*/
#[\PHPUnit\Framework\Attributes\DataProvider('dataForHashSensitiveHeaderData')]
public function testHashSensitiveHeaderData(string $authorization, array $headers): void
{
$hashedHeaders = oAuthHelper::sanitizeHeaderData($headers);
$this->assertStringContainsString(sprintf('Authorization: %s [REDACTED]', $authorization), $hashedHeaders[0]);
}
/**
* @return \Generator<string, array<int, string|array<int, string>>>
*/
public static function dataForHashSensitiveHeaderData(): \Generator
{
yield 'For Bearer' => [
'Bearer',
[
'Authorization: Bearer SME-ASA',
],
];
yield 'For Basic' => [
'Basic',
[
'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l',
],
];
}
}

View File

@@ -0,0 +1,183 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Integration;
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\RequestOptions;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use Psr\Http\Message\ResponseInterface;
class AbstractIntegrationTest extends AbstractIntegrationTestCase
{
public function testPopulatedLeadDataReturnsIntAndNotDncEntityForMauticContactIsContactableByEmail(): void
{
/**
* @var MockObject&AbstractIntegration
*/
$integration = $this->getMockBuilder(AbstractIntegration::class)
->setConstructorArgs([
$this->dispatcher,
$this->cache,
$this->em,
$this->request,
$this->router,
$this->translator,
$this->logger,
$this->encryptionHelper,
$this->leadModel,
$this->companyModel,
$this->pathsHelper,
$this->notificationModel,
$this->fieldModel,
$this->integrationEntityModel,
$this->doNotContact,
$this->fieldsWithUniqueIdentifier,
])
->onlyMethods(['getName', 'getAuthenticationType', 'getAvailableLeadFields'])
->getMock();
$integration->method('getAvailableLeadFields')
->willReturn(
[
'dnc' => [
'type' => 'bool',
'required' => false,
'label' => 'DNC',
],
]
);
$this->assertEquals(
['dnc' => 0],
$integration->populateLeadData(
['id' => 1],
[
'leadFields' => [
'dnc' => 'mauticContactIsContactableByEmail',
],
]
)
);
}
/**
* @param mixed[] $parameters
* @param mixed[] $settings
*/
#[\PHPUnit\Framework\Attributes\DataProvider('requestProvider')]
public function testMakeRequest(string $uri, array $parameters, string $method, array $settings, object $assertRequest): void
{
/**
* @var MockObject&AbstractIntegration
*/
$integration = $this->getMockBuilder(AbstractIntegration::class)
->setConstructorArgs([
$this->dispatcher,
$this->cache,
$this->em,
$this->request,
$this->router,
$this->translator,
$this->logger,
$this->encryptionHelper,
$this->leadModel,
$this->companyModel,
$this->pathsHelper,
$this->notificationModel,
$this->fieldModel,
$this->integrationEntityModel,
$this->doNotContact,
$this->fieldsWithUniqueIdentifier,
])
->onlyMethods(['getName', 'getAuthenticationType', 'makeHttpClient'])
->getMock();
$integration->method('makeHttpClient')
->willReturn(
new class($assertRequest) extends Client {
public function __construct(
private object $assertRequest,
) {
}
/**
* @param mixed[] $options
*/
public function request(string $method, $uri = '', array $options = []): ResponseInterface
{
$this->assertRequest->assert($method, $uri, $options);
return new Response();
}
}
);
$this->assertEquals([], $integration->makeRequest($uri, $parameters, $method, $settings));
}
/**
* @return iterable<mixed[]>
*/
public static function requestProvider(): iterable
{
// Test with JSON.
yield [
'https://some.uri',
['this will be' => 'encoded to json string'],
'POST',
[
'ignore_event_dispatch' => true,
'encode_parameters' => 'json',
],
new class {
/**
* @param mixed[] $options
*/
public function assert(string $method, string $uri = '', array $options = []): void
{
Assert::assertSame('POST', $method);
Assert::assertSame('https://some.uri', $uri);
Assert::assertSame(
[
RequestOptions::BODY => '{"this will be":"encoded to json string"}',
'headers' => ['Content-Type' => 'application/json'],
'timeout' => 10,
],
$options
);
}
},
];
// Test with form params.
yield [
'https://some.uri',
['this will be' => 'encoded to form array'],
'POST',
['ignore_event_dispatch' => true],
new class {
/**
* @param mixed[] $options
*/
public function assert(string $method, string $uri = '', array $options = []): void
{
Assert::assertSame('POST', $method);
Assert::assertSame('https://some.uri', $uri);
Assert::assertSame(
[
RequestOptions::FORM_PARAMS => ['this will be' => 'encoded to form array'],
'headers' => [],
'timeout' => 10,
],
$options
);
}
},
];
}
}

View File

@@ -0,0 +1,134 @@
<?php
namespace Mautic\PluginBundle\Tests\Integration;
use Doctrine\ORM\EntityManager;
use Mautic\CoreBundle\Helper\CacheStorageHelper;
use Mautic\CoreBundle\Helper\EncryptionHelper;
use Mautic\CoreBundle\Helper\PathsHelper;
use Mautic\CoreBundle\Model\NotificationModel;
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
use Mautic\LeadBundle\Model\CompanyModel;
use Mautic\LeadBundle\Model\DoNotContact;
use Mautic\LeadBundle\Model\FieldModel;
use Mautic\LeadBundle\Model\LeadModel;
use Mautic\PluginBundle\Model\IntegrationEntityModel;
use Monolog\Logger;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Router;
use Symfony\Contracts\Translation\TranslatorInterface;
class AbstractIntegrationTestCase extends TestCase
{
/**
* @var EventDispatcherInterface&MockObject
*/
protected $dispatcher;
/**
* @var CacheStorageHelper&MockObject
*/
protected $cache;
/**
* @var EntityManager&MockObject
*/
protected $em;
/**
* @var Session&MockObject
*/
protected $session;
/**
* @var RequestStack&MockObject
*/
protected $request;
/**
* @var Router&MockObject
*/
protected $router;
/**
* @var TranslatorInterface&MockObject
*/
protected $translator;
/**
* @var Logger&MockObject
*/
protected $logger;
/**
* @var EncryptionHelper&MockObject
*/
protected $encryptionHelper;
/**
* @var LeadModel&MockObject
*/
protected $leadModel;
/**
* @var CompanyModel&MockObject
*/
protected $companyModel;
/**
* @var PathsHelper&MockObject
*/
protected $pathsHelper;
/**
* @var NotificationModel&MockObject
*/
protected $notificationModel;
/**
* @var FieldModel&MockObject
*/
protected $fieldModel;
/**
* @var IntegrationEntityModel&MockObject
*/
protected $integrationEntityModel;
/**
* @var DoNotContact&MockObject
*/
protected $doNotContact;
/**
* @var MockObject&FieldsWithUniqueIdentifier
*/
protected MockObject $fieldsWithUniqueIdentifier;
protected function setUp(): void
{
parent::setUp();
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->cache = $this->createMock(CacheStorageHelper::class);
$this->em = $this->createMock(EntityManager::class);
$this->session = $this->createMock(Session::class);
$this->request = $this->createMock(RequestStack::class);
$this->router = $this->createMock(Router::class);
$this->translator = $this->createMock(TranslatorInterface::class);
$this->logger = $this->createMock(Logger::class);
$this->encryptionHelper = $this->createMock(EncryptionHelper::class);
$this->leadModel = $this->createMock(LeadModel::class);
$this->companyModel = $this->createMock(CompanyModel::class);
$this->pathsHelper = $this->createMock(PathsHelper::class);
$this->notificationModel = $this->createMock(NotificationModel::class);
$this->fieldModel = $this->createMock(FieldModel::class);
$this->integrationEntityModel = $this->createMock(IntegrationEntityModel::class);
$this->doNotContact = $this->createMock(DoNotContact::class);
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
}
}

View File

@@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
namespace Mautic\PluginBundle\Tests\Integration;
use GuzzleHttp\Client;
class ClientFactory
{
private Client $httpClient;
public function __construct(Client $httpClient)
{
$this->httpClient = $httpClient;
}
public function __invoke(): Client
{
return $this->httpClient;
}
}