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,60 @@
<?php
namespace Mautic\IntegrationsBundle\Tests\Unit;
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 as DoNotContactModel;
use Mautic\LeadBundle\Model\FieldModel;
use Mautic\LeadBundle\Model\LeadModel;
use Mautic\PluginBundle\Integration\AbstractIntegration;
use Mautic\PluginBundle\Model\IntegrationEntityModel;
use Monolog\Logger;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\Routing\Router;
use Symfony\Contracts\Translation\TranslatorInterface;
class AbstractIntegrationTest extends TestCase
{
public function testParseCallbackResponseWithUTF8StringThatContainsControlChars(): void
{
$integrationDouble = $this->buildAbstractIntegrationDouble();
$jsonString = <<<JSON
{
"webinars": [
{
"topic": "【】 "
}
]
}
JSON;
$json = $integrationDouble->parseCallbackResponse($jsonString);
self::assertArrayHasKey('webinars', $json);
}
private function buildAbstractIntegrationDouble(): AbstractIntegration
{
// creating a double since we can't instantiate
// we also need to expose some things for better unit test coverage
return new class($this->createMock(EventDispatcherInterface::class), $this->createMock(CacheStorageHelper::class), $this->createMock(EntityManager::class), $this->createMock(RequestStack::class), $this->createMock(Router::class), $this->createMock(TranslatorInterface::class), $this->createMock(Logger::class), $this->createMock(EncryptionHelper::class), $this->createMock(LeadModel::class), $this->createMock(CompanyModel::class), $this->createMock(PathsHelper::class), $this->createMock(NotificationModel::class), $this->createMock(FieldModel::class), $this->createMock(IntegrationEntityModel::class), $this->createMock(DoNotContactModel::class), $this->createMock(FieldsWithUniqueIdentifier::class)) extends AbstractIntegration {
public function getName(): string
{
return 'double';
}
public function getAuthenticationType(): string
{
return 'none';
}
};
}
}

View File

@@ -0,0 +1,135 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Provider\ApiKey;
use GuzzleHttp\Exception\ConnectException;
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\HeaderCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\Credentials\ParameterCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\ApiKey\HttpFactory;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Exception\InvalidCredentialsException;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
use PHPUnit\Framework\TestCase;
class HttpFactoryTest extends TestCase
{
public function testType(): void
{
$this->assertEquals('api_key', (new HttpFactory())->getAuthType());
}
public function testInvalidCredentialsThrowsException(): void
{
$this->expectException(InvalidCredentialsException::class);
$credentials = new class implements AuthCredentialsInterface {
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingCredentialsThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements HeaderCredentialsInterface {
public function getApiKey(): string
{
return '';
}
public function getKeyName(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testInstantiatedClientIsReturned(): void
{
$credentials = new class implements HeaderCredentialsInterface {
public function getApiKey(): string
{
return 'abc';
}
public function getKeyName(): string
{
return '123';
}
};
$factory = new HttpFactory();
$client1 = $factory->getClient($credentials);
$client2 = $factory->getClient($credentials);
$this->assertTrue($client1 === $client2);
$credential2 = new class implements HeaderCredentialsInterface {
public function getApiKey(): string
{
return '123';
}
public function getKeyName(): string
{
return 'abc';
}
};
$client3 = $factory->getClient($credential2);
$this->assertFalse($client1 === $client3);
}
public function testHeaderCredentialsSetsHeader(): void
{
$credentials = new class implements HeaderCredentialsInterface {
public function getApiKey(): string
{
return '123';
}
public function getKeyName(): string
{
return 'abc';
}
};
$factory = new HttpFactory();
$client = $factory->getClient($credentials);
$headers = $client->getConfig('headers'); /** @phpstan-ignore-line Deprecated. Must be refactored for Guzzle 8 */
$this->assertArrayHasKey('abc', $headers);
$this->assertEquals('123', $headers['abc']);
}
public function testParameterCredentialsAppendsToken(): void
{
$credentials = new class implements ParameterCredentialsInterface {
public function getApiKey(): string
{
return '123';
}
public function getKeyName(): string
{
return 'abc';
}
};
$factory = new HttpFactory();
$client = $factory->getClient($credentials);
try {
// Triggering an exception so we can extract the request
$client->request('get', 'foobar');
} catch (ConnectException $exception) {
$query = $exception->getRequest()->getUri()->getQuery();
$this->assertEquals('abc=123', $query);
}
}
}

View File

@@ -0,0 +1,122 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Provider\BasicAuth;
use GuzzleHttp\Exception\ConnectException;
use Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\CredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\BasicAuth\HttpFactory;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
use PHPUnit\Framework\TestCase;
class HttpFactoryTest extends TestCase
{
public function testType(): void
{
$this->assertEquals('basic_auth', (new HttpFactory())->getAuthType());
}
public function testMissingUsernameThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements CredentialsInterface {
public function getUsername(): string
{
return '';
}
public function getPassword(): string
{
return '123';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingPasswordThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements CredentialsInterface {
public function getUsername(): string
{
return '123';
}
public function getPassword(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testInstantiatedClientIsReturned(): void
{
$credentials = new class implements CredentialsInterface {
public function getUsername(): string
{
return 'foo';
}
public function getPassword(): string
{
return 'bar';
}
};
$factory = new HttpFactory();
$client1 = $factory->getClient($credentials);
$client2 = $factory->getClient($credentials);
$this->assertTrue($client1 === $client2);
$credentials2 = new class implements CredentialsInterface {
public function getUsername(): string
{
return 'bar';
}
public function getPassword(): string
{
return 'foo';
}
};
$client3 = $factory->getClient($credentials2);
$this->assertFalse($client1 === $client3);
}
public function testHeaderIsSet(): void
{
$credentials = new class implements CredentialsInterface {
public function getUsername(): string
{
return 'foo';
}
public function getPassword(): string
{
return 'bar';
}
};
$factory = new HttpFactory();
$client = $factory->getClient($credentials);
try {
// Triggering an exception so we can extract the request
$client->request('get', 'foobar');
} catch (ConnectException $exception) {
$headers = $exception->getRequest()->getHeaders();
$this->assertArrayHasKey('Authorization', $headers);
$this->assertEquals('Basic '.base64_encode('foo:bar'), $headers['Authorization'][0]);
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Provider\Oauth1aTwoLegged;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged\CredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth1aTwoLegged\HttpFactory;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
use PHPUnit\Framework\TestCase;
class HttpFactoryTest extends TestCase
{
public function testType(): void
{
$this->assertEquals('oauth1a_two_legged', (new HttpFactory())->getAuthType());
}
public function testGetClientWithEmptyCredentials(): void
{
$credentials = $this->createMock(CredentialsInterface::class);
$httpFactory = new HttpFactory();
$this->expectException(PluginNotConfiguredException::class);
$httpFactory->getClient($credentials);
}
public function testGetClientWithFullCredentials(): void
{
$credentials = $this->createMock(CredentialsInterface::class);
$credentials->method('getConsumerKey')->willReturn('ConsumerKeyValue');
$credentials->method('getConsumerSecret')->willReturn('ConsumerSecretValue');
$credentials->method('getToken')->willReturn('TokenValue');
$credentials->method('getTokenSecret')->willReturn('TokenSecretValue');
$credentials->method('getAuthUrl')->willReturn('AuthUrlValue');
$httpFactory = new HttpFactory();
$client = $httpFactory->getClient($credentials);
$config = $client->getConfig(); /** @phpstan-ignore-line Deprecated. Must be refactored for Guzzle 8 */
$this->assertSame('oauth', $config['auth']);
$this->assertSame('AuthUrlValue', $config['base_uri']->getPath());
$this->assertTrue($config['handler']->hasHandler());
}
}

View File

@@ -0,0 +1,388 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Provider\Oauth2ThreeLegged;
use GuzzleHttp\ClientInterface;
use kamermans\OAuth2\OAuth2Middleware;
use kamermans\OAuth2\Persistence\TokenPersistenceInterface as KamermansTokenPersistenceInterface;
use kamermans\OAuth2\Signer\AccessToken\SignerInterface as AccessTokenSigner;
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CodeInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\RedirectUriInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\ScopeInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigCredentialsSignerInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenPersistenceInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenSignerInterface;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
use PHPUnit\Framework\TestCase;
class HttpFactoryTest extends TestCase
{
public function testType(): void
{
$this->assertEquals('oauth2_three_legged', (new HttpFactory())->getAuthType());
}
public function testMissingAuthorizationUrlThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return '';
}
public function getTokenUrl(): string
{
return '';
}
public function getClientId(): string
{
return '';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingTokenUrlThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return '';
}
public function getClientId(): string
{
return '';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testBaseURISetOnBaseUriAwareCredentials(): void
{
$credentials = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return 'http://token.url';
}
public function getClientId(): string
{
return 'bar';
}
public function getClientSecret(): string
{
return 'foo';
}
public function getCode(): string
{
return 'auth_code';
}
public function getRedirectUri(): string
{
return 'http://redirect.url';
}
public function getScope(): string
{
return 'scope';
}
public function getBaseUri(): string
{
return 'https://mautic.com';
}
};
$client = (new HttpFactory())->getClient($credentials);
/**
* Even though the method getConfig is deprecated it won't get deprecated
* https://github.com/guzzle/guzzle/issues/3114#issuecomment-1627228395.
*/
/** @phpstan-ignore-next-line */
$this->assertEquals('https://mautic.com', (string) $client->getConfig('base_uri'));
}
public function testMissingClientIdThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return 'http://token.url';
}
public function getClientId(): string
{
return '';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingClientSecretThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return 'http://token.url';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testInstantiatedClientIsReturned(): void
{
$credentials = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return 'http://token.url';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return 'bar';
}
};
$factory = new HttpFactory();
$client1 = $factory->getClient($credentials);
$client2 = $factory->getClient($credentials);
$this->assertTrue($client1 === $client2);
$credentials2 = new class implements CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return 'http://token.url';
}
public function getClientId(): string
{
return 'bar';
}
public function getClientSecret(): string
{
return 'foo';
}
};
$client3 = $factory->getClient($credentials2);
$this->assertFalse($client1 === $client3);
}
public function testReAuthClientConfiguration(): void
{
$credentials = $this->getCredentials();
$client = (new HttpFactory())->getClient($credentials);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$grantType = $this->getProperty($reflectedMiddleware, $middleware, 'grantType');
$reflectedGrantType = new \ReflectionClass($grantType);
$reauthConfig = $this->getProperty($reflectedGrantType, $grantType, 'config');
$expectedConfig = [
'client_id' => $credentials->getClientId(),
'client_secret' => $credentials->getClientSecret(),
'code' => $credentials->getCode(),
'redirect_uri' => $credentials->getRedirectUri(),
'scope' => $credentials->getScope(),
];
$this->assertEquals($expectedConfig, $reauthConfig->toArray());
}
public function testClientConfiguration(): void
{
$credentials = $this->getCredentials();
$signerInterface = $this->createMock(SignerInterface::class);
$kamermansTokenPersistence = $this->createMock(KamermansTokenPersistenceInterface::class);
$accessTokenSigner = $this->createMock(AccessTokenSigner::class);
$clientCredentialSigner = $this->createMock(ConfigCredentialsSignerInterface::class);
$clientCredentialSigner->expects($this->once())
->method('getCredentialsSigner')
->willReturn($signerInterface);
$client = (new HttpFactory())->getClient($credentials, $clientCredentialSigner);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$this->assertTrue($this->getProperty($reflectedMiddleware, $middleware, 'clientCredentialsSigner') === $signerInterface);
$tokenPersistence = $this->createMock(ConfigTokenPersistenceInterface::class);
$tokenPersistence->expects($this->once())
->method('getTokenPersistence')
->willReturn($kamermansTokenPersistence);
$client = (new HttpFactory())->getClient($credentials, $tokenPersistence);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$this->assertTrue($this->getProperty($reflectedMiddleware, $middleware, 'tokenPersistence') === $kamermansTokenPersistence);
$tokenPersistence = $this->createMock(ConfigTokenSignerInterface::class);
$tokenPersistence->expects($this->once())
->method('getTokenSigner')
->willReturn($accessTokenSigner);
$client = (new HttpFactory())->getClient($credentials, $tokenPersistence);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$this->assertTrue($this->getProperty($reflectedMiddleware, $middleware, 'accessTokenSigner') === $accessTokenSigner);
}
/**
* @throws \ReflectionException
*/
private function extractMiddleware(ClientInterface $client): OAuth2Middleware
{
/** @phpstan-ignore-next-line */
$handler = $client->getConfig()['handler'];
$reflection = new \ReflectionClass($handler);
$property = $reflection->getProperty('stack');
$property->setAccessible(true);
$stack = $property->getValue($handler);
/** @var OAuth2Middleware $oauthMiddleware */
$oauthMiddleware = array_pop($stack);
return $oauthMiddleware[0];
}
private function getProperty(\ReflectionClass $reflection, $object, string $name)
{
$property = $reflection->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
/**
* @return CredentialsInterface|CodeInterface|RedirectUriInterface|ScopeInterface
*/
private function getCredentials(): CredentialsInterface
{
return new class implements CredentialsInterface, CodeInterface, RedirectUriInterface, ScopeInterface {
public function getAuthorizationUrl(): string
{
return 'http://auth.url';
}
public function getTokenUrl(): string
{
return 'http://token.url';
}
public function getClientId(): string
{
return 'bar';
}
public function getClientSecret(): string
{
return 'foo';
}
public function getCode(): string
{
return 'auth_code';
}
public function getRedirectUri(): string
{
return 'http://redirect.url';
}
public function getScope(): string
{
return 'scope';
}
};
}
}

View File

@@ -0,0 +1,432 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Provider\Oauth2TwoLegged;
use GuzzleHttp\ClientInterface;
use kamermans\OAuth2\GrantType\ClientCredentials;
use kamermans\OAuth2\GrantType\PasswordCredentials;
use kamermans\OAuth2\OAuth2Middleware;
use kamermans\OAuth2\Persistence\TokenPersistenceInterface as KamermansTokenPersistenceInterface;
use kamermans\OAuth2\Signer\AccessToken\SignerInterface as AccessTokenSigner;
use kamermans\OAuth2\Signer\ClientCredentials\SignerInterface;
use Mautic\IntegrationsBundle\Auth\Provider\AuthCredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\Credentials\CredentialsInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\ClientCredentialsGrantInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\PasswordCredentialsGrantInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\ScopeInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\Credentials\StateInterface;
use Mautic\IntegrationsBundle\Auth\Provider\Oauth2TwoLegged\HttpFactory;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigCredentialsSignerInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenPersistenceInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\ConfigAccess\ConfigTokenSignerInterface;
use Mautic\IntegrationsBundle\Exception\InvalidCredentialsException;
use Mautic\IntegrationsBundle\Exception\PluginNotConfiguredException;
use PHPUnit\Framework\TestCase;
class HttpFactoryTest extends TestCase
{
public function testType(): void
{
$this->assertEquals('oauth2_two_legged', (new HttpFactory())->getAuthType());
}
public function testInvalidCredentialsThrowsException(): void
{
$this->expectException(InvalidCredentialsException::class);
$credentials = new class implements AuthCredentialsInterface {
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingAuthorizationUrlThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements ClientCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return '';
}
public function getClientId(): string
{
return '';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingClientIdThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements ClientCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return '';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingClientSecretIdThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements ClientCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingUsernameThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements PasswordCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return 'bar';
}
public function getUsername(): string
{
return '';
}
public function getPassword(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testMissingPasswordThrowsException(): void
{
$this->expectException(PluginNotConfiguredException::class);
$credentials = new class implements PasswordCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return 'bar';
}
public function getUsername(): string
{
return 'foo';
}
public function getPassword(): string
{
return '';
}
};
(new HttpFactory())->getClient($credentials);
}
public function testInstantiatedClientIsReturned(): void
{
$credentials = new class implements ClientCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return 'bar';
}
};
$factory = new HttpFactory();
$client1 = $factory->getClient($credentials);
$client2 = $factory->getClient($credentials);
$this->assertTrue($client1 === $client2);
$credentials2 = new class implements ClientCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'bar';
}
public function getClientSecret(): string
{
return 'foo';
}
};
$client3 = $factory->getClient($credentials2);
$this->assertFalse($client1 === $client3);
}
public function testReAuthClientConfiguration(): void
{
$credentials = $this->getCredentials();
$client = (new HttpFactory())->getClient($credentials);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$grantType = $this->getProperty($reflectedMiddleware, $middleware, 'grantType');
$reflectedGrantType = new \ReflectionClass($grantType);
$reauthConfig = $this->getProperty($reflectedGrantType, $grantType, 'config');
$expectedConfig = [
'client_id' => $credentials->getClientId(),
'client_secret' => $credentials->getClientSecret(),
'scope' => $credentials->getScope(),
'state' => $credentials->getState(),
'username' => $credentials->getUsername(),
'password' => $credentials->getPassword(),
];
$this->assertEquals($expectedConfig, $reauthConfig->toArray());
}
public function testPasswordGrantTypeIsUsed(): void
{
$credentials = new class implements PasswordCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return 'bar';
}
public function getUsername(): string
{
return 'username';
}
public function getPassword(): string
{
return 'password';
}
};
$client = (new HttpFactory())->getClient($credentials);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$grantType = $this->getProperty($reflectedMiddleware, $middleware, 'grantType');
$this->assertInstanceOf(PasswordCredentials::class, $grantType);
}
public function testClientCredentialsGrantTypeIsUsed(): void
{
$credentials = new class implements ClientCredentialsGrantInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'foo';
}
public function getClientSecret(): string
{
return 'bar';
}
};
$client = (new HttpFactory())->getClient($credentials);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$grantType = $this->getProperty($reflectedMiddleware, $middleware, 'grantType');
$this->assertInstanceOf(ClientCredentials::class, $grantType);
}
public function testClientConfiguration(): void
{
$credentials = $this->getCredentials();
$signerInterface = $this->createMock(SignerInterface::class);
$kamermansTokenPersistence = $this->createMock(KamermansTokenPersistenceInterface::class);
$accessTokenSigner = $this->createMock(AccessTokenSigner::class);
$clientCredentialSigner = $this->createMock(ConfigCredentialsSignerInterface::class);
$clientCredentialSigner->expects($this->once())
->method('getCredentialsSigner')
->willReturn($signerInterface);
$client = (new \Mautic\IntegrationsBundle\Auth\Provider\Oauth2ThreeLegged\HttpFactory())->getClient($credentials, $clientCredentialSigner);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$this->assertTrue($this->getProperty($reflectedMiddleware, $middleware, 'clientCredentialsSigner') === $signerInterface);
$tokenPersistence = $this->createMock(ConfigTokenPersistenceInterface::class);
$tokenPersistence->expects($this->once())
->method('getTokenPersistence')
->willReturn($kamermansTokenPersistence);
$client = (new HttpFactory())->getClient($credentials, $tokenPersistence);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$this->assertTrue($this->getProperty($reflectedMiddleware, $middleware, 'tokenPersistence') === $kamermansTokenPersistence);
$tokenPersistence = $this->createMock(ConfigTokenSignerInterface::class);
$tokenPersistence->expects($this->once())
->method('getTokenSigner')
->willReturn($accessTokenSigner);
$client = (new HttpFactory())->getClient($credentials, $tokenPersistence);
$middleware = $this->extractMiddleware($client);
$reflectedMiddleware = new \ReflectionClass($middleware);
$this->assertTrue($this->getProperty($reflectedMiddleware, $middleware, 'accessTokenSigner') === $accessTokenSigner);
}
/**
* @throws \ReflectionException
*/
private function extractMiddleware(ClientInterface $client): OAuth2Middleware
{
$handler = $client->getConfig()['handler']; /** @phpstan-ignore-line Deprecated. Must be refactored for Guzzle 8 */
$reflection = new \ReflectionClass($handler);
$property = $reflection->getProperty('stack');
$property->setAccessible(true);
$stack = $property->getValue($handler);
/** @var OAuth2Middleware $oauthMiddleware */
$oauthMiddleware = array_pop($stack);
return $oauthMiddleware[0];
}
private function getProperty(\ReflectionClass $reflection, $object, string $name)
{
$property = $reflection->getProperty($name);
$property->setAccessible(true);
return $property->getValue($object);
}
/**
* @return PasswordCredentialsGrantInterface|StateInterface|ScopeInterface
*/
private function getCredentials(): PasswordCredentialsGrantInterface
{
return new class implements PasswordCredentialsGrantInterface, StateInterface, ScopeInterface, CredentialsInterface {
public function getAuthorizationUrl(): string
{
return 'http://test.com';
}
public function getClientId(): string
{
return 'bar';
}
public function getUsername(): string
{
return 'username';
}
public function getPassword(): string
{
return 'password';
}
public function getState(): string
{
return 'state';
}
public function getScope(): string
{
return 'scope';
}
public function getClientSecret(): string
{
return 'secret';
}
public function getTokenUrl(): string
{
return 'tokenurl';
}
};
}
}

View File

@@ -0,0 +1,128 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Support\Oauth2\Token;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationToken;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationTokenFactory;
use PHPUnit\Framework\TestCase;
class IntegrationTokenFactoryTest extends TestCase
{
public function testTokenGeneratedWithExpiresIn(): void
{
$factory = new IntegrationTokenFactory();
$data = [
'access_token' => '123',
'refresh_token' => '456',
'expires_in' => 10,
];
$token = $factory($data);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($data['refresh_token'], $token->getRefreshToken());
$this->assertFalse($token->isExpired());
$this->assertEquals(time() + 10, $token->getExpiresAt());
}
public function testTokenGeneratedWithExpiresAt(): void
{
$factory = new IntegrationTokenFactory();
$data = [
'access_token' => '123',
'refresh_token' => '456',
'expires_at' => time() + 10,
];
$token = $factory($data);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($data['refresh_token'], $token->getRefreshToken());
$this->assertFalse($token->isExpired());
$this->assertEquals($data['expires_at'], $token->getExpiresAt());
}
public function testTokenGeneratedWithExpires(): void
{
$factory = new IntegrationTokenFactory();
$data = [
'access_token' => '123',
'refresh_token' => '456',
'expires' => 10,
];
$token = $factory($data);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($data['refresh_token'], $token->getRefreshToken());
$this->assertFalse($token->isExpired());
$this->assertEquals(time() + 10, $token->getExpiresAt());
}
public function testTokenGeneratedWithDefaultExpires(): void
{
$factory = new IntegrationTokenFactory([], 100);
$data = [
'access_token' => '123',
'refresh_token' => '456',
];
$token = $factory($data);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($data['refresh_token'], $token->getRefreshToken());
$this->assertFalse($token->isExpired());
$this->assertEquals(time() + 100, $token->getExpiresAt());
}
public function testTokenGeneratedWithUnexpiredTokenByDefault(): void
{
$factory = new IntegrationTokenFactory();
$data = [
'access_token' => '123',
'refresh_token' => '456',
];
$token = $factory($data);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($data['refresh_token'], $token->getRefreshToken());
$this->assertFalse($token->isExpired());
$this->assertEquals(0, $token->getExpiresAt());
}
public function testTokenGeneratedWithPreviousRefreshToken(): void
{
$factory = new IntegrationTokenFactory();
$data = [
'access_token' => '123',
];
$previousToken = new IntegrationToken('789', '456');
$token = $factory($data, $previousToken);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($previousToken->getRefreshToken(), $token->getRefreshToken());
$this->assertFalse($token->isExpired());
}
public function testTokenGeneratedWithExtraData(): void
{
$factory = new IntegrationTokenFactory(['foo']);
$data = [
'access_token' => '123',
'refresh_token' => '456',
'foo' => 'bar',
'bar' => 'foo',
];
$token = $factory($data);
$this->assertEquals($data['access_token'], $token->getAccessToken());
$this->assertEquals($data['refresh_token'], $token->getRefreshToken());
$this->assertFalse($token->isExpired());
$this->assertEquals(['foo' => 'bar'], $token->getExtraData());
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Support\Oauth2\Token;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationToken;
use PHPUnit\Framework\TestCase;
class IntegrationTokenTest extends TestCase
{
public function testGetters(): void
{
$expires = time() + 100;
$extraData = ['foo' => 'bar'];
$token = new IntegrationToken('accessToken', 'refreshToken', $expires, $extraData);
$this->assertEquals('accessToken', $token->getAccessToken());
$this->assertEquals('refreshToken', $token->getRefreshToken());
$this->assertEquals($expires, $token->getExpiresAt());
$this->assertEquals($extraData, $token->getExtraData());
}
public function testIsExpired(): void
{
$token = new IntegrationToken('accessToken', 'refreshToken', time() - 100);
$this->assertTrue($token->isExpired());
}
public function testIsExpiredIfAccessTokenIsMissing(): void
{
$token = new IntegrationToken('', 'refreshToken');
$this->assertTrue($token->isExpired());
}
public function testIsNotExpired(): void
{
$token = new IntegrationToken('accessToken', 'refreshToken', time() + 100);
$this->assertFalse($token->isExpired());
}
}

View File

@@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Support\Oauth2\Token;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistenceFactory;
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
use Mautic\PluginBundle\Entity\Integration;
use PHPUnit\Framework\TestCase;
class TokenPersistenceFactoryTest extends TestCase
{
private \PHPUnit\Framework\MockObject\MockObject $integrationsHelper;
private \PHPUnit\Framework\MockObject\MockObject $integration;
public function setup(): void
{
$this->integrationsHelper = $this->createMock(IntegrationsHelper::class);
$this->integration = $this->createMock(Integration::class);
}
public function testCreate(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$expiresAt = 10;
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
];
$this->integration->expects($this->any())
->method('getApiKeys')
->willReturn($apiKeys);
$factory = new TokenPersistenceFactory($this->integrationsHelper);
$tokenPersistence = $factory->create($this->integration);
$this->assertTrue($tokenPersistence->hasToken());
}
public function testCreateWithInvalidToken(): void
{
$accessToken = null;
$refreshToken = 'refresh_token';
$expiresAt = 10;
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
];
$this->integration->expects($this->any())
->method('getApiKeys')
->willReturn($apiKeys);
$factory = new TokenPersistenceFactory($this->integrationsHelper);
$tokenPersistence = $factory->create($this->integration);
$this->assertFalse($tokenPersistence->hasToken());
}
}

View File

@@ -0,0 +1,285 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Auth\Support\Oauth2\Token;
use kamermans\OAuth2\Token\RawToken;
use kamermans\OAuth2\Token\RawTokenFactory;
use kamermans\OAuth2\Token\TokenInterface;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\IntegrationToken;
use Mautic\IntegrationsBundle\Auth\Support\Oauth2\Token\TokenPersistence;
use Mautic\IntegrationsBundle\Exception\IntegrationNotSetException;
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
use Mautic\PluginBundle\Entity\Integration;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class TokenPersistenceTest extends TestCase
{
/**
* @var MockObject|IntegrationsHelper
*/
private MockObject $integrationsHelper;
private TokenPersistence $tokenPersistence;
public function setUp(): void
{
$this->integrationsHelper = $this->createMock(IntegrationsHelper::class);
$this->tokenPersistence = new TokenPersistence($this->integrationsHelper);
parent::setUp();
}
public function testIntegrationNotSetRestoreToken(): void
{
$this->expectException(IntegrationNotSetException::class);
$token = $this->createMock(TokenInterface::class);
$this->tokenPersistence->restoreToken($token);
}
public function testRestoreToken(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$expiresAt = 10;
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
];
$factory = new RawTokenFactory();
$tokenFromApi = $factory([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
]);
$integration = $this->createMock(Integration::class);
$integration->expects($this->once())
->method('getApiKeys')
->willReturn($apiKeys);
$this->tokenPersistence->setIntegration($integration);
$newToken = $this->tokenPersistence->restoreToken($tokenFromApi);
$this->assertSame($tokenFromApi->getAccessToken(), $newToken->getAccessToken());
$this->assertSame($tokenFromApi->getRefreshToken(), $newToken->getRefreshToken());
}
public function testIntegrationNotSetSaveToken(): void
{
$this->expectException(IntegrationNotSetException::class);
$token = $this->createMock(TokenInterface::class);
$this->tokenPersistence->saveToken($token);
}
public function testSaveToken(): void
{
$oldApiKeys = [
'access_token' => 'old_access_token',
'something' => 'something',
];
$newApiKeys = [
'access_token' => 'access_token',
'refresh_token' => 'refresh_token',
'expires_at' => '0',
];
$extraData = [
'instance_url' => 'abc.123.com',
];
$token = new IntegrationToken($newApiKeys['access_token'], $newApiKeys['refresh_token'], $newApiKeys['expires_at'], $extraData);
$integration = $this->createMock(Integration::class);
$newApiKeys = array_merge($oldApiKeys, $extraData, $newApiKeys);
$integration->expects($this->exactly(2))
->method('getApiKeys')
->willReturnOnConsecutiveCalls($oldApiKeys, $newApiKeys);
$integration->expects($this->once())
->method('setApiKeys')
->with($newApiKeys);
$this->tokenPersistence->setIntegration($integration);
$this->integrationsHelper->expects($this->once())
->method('saveIntegrationConfiguration');
$this->tokenPersistence->saveToken($token);
$this->assertTrue($this->tokenPersistence->hasToken());
}
public function testIntegrationNotSetDeleteToken(): void
{
$this->expectException(IntegrationNotSetException::class);
$token = $this->createMock(TokenInterface::class);
$this->tokenPersistence->saveToken($token);
}
public function testDeleteToken(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$expiresAt = 10;
$token = new RawToken($accessToken, $refreshToken, $expiresAt);
$expected = [
'leaveMe' => 'something',
];
$apiKeys = array_merge(
[
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
],
$expected
);
$integration = new Integration();
$integration->setApiKeys($apiKeys);
$this->tokenPersistence->setIntegration($integration);
$this->integrationsHelper->expects($this->exactly(2))
->method('saveIntegrationConfiguration');
$this->tokenPersistence->saveToken($token);
$this->assertTrue($this->tokenPersistence->hasToken());
$this->tokenPersistence->deleteToken();
$this->assertFalse($this->tokenPersistence->hasToken());
$apiKeys = $integration->getApiKeys();
$this->assertFalse(isset($apiKeys['access_token']));
$this->assertFalse(isset($apiKeys['expires_in']));
$newToken = $this->tokenPersistence->restoreToken($token);
$this->assertTrue($newToken->isExpired());
$this->assertEmpty($newToken->getAccessToken());
}
public function testHasToken(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$expiresAt = 10;
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
];
$integration = $this->createMock(Integration::class);
$integration->method('getApiKeys')
->willReturnOnConsecutiveCalls(null, $apiKeys, ['access_token' => $accessToken], null, null);
$this->tokenPersistence->setIntegration($integration);
$this->assertFalse($this->tokenPersistence->hasToken());
$token = new RawToken($accessToken, $refreshToken, $expiresAt);
$this->tokenPersistence->saveToken($token);
$this->assertTrue($this->tokenPersistence->hasToken());
$token = new RawToken();
$this->tokenPersistence->saveToken($token);
$this->assertFalse($this->tokenPersistence->hasToken());
}
public function testRestoreTokenSetsExpirationIfKnown(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$expiresAt = time() + 100;
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
];
$factory = new RawTokenFactory();
$tokenFromApi = $factory([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
]);
$integration = $this->createMock(Integration::class);
$integration->expects($this->once())
->method('getApiKeys')
->willReturn($apiKeys);
$this->tokenPersistence->setIntegration($integration);
$newToken = $this->tokenPersistence->restoreToken($tokenFromApi);
$this->assertSame($apiKeys['expires_at'], $newToken->getExpiresAt());
$this->assertFalse($newToken->isExpired());
}
public function testRestoreTokenRestoresExpirationToNotExpiredWhenNotExplicitlyDefined(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
];
$factory = new RawTokenFactory();
$tokenFromApi = $factory([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
]);
$integration = $this->createMock(Integration::class);
$integration->expects($this->once())
->method('getApiKeys')
->willReturn($apiKeys);
$this->tokenPersistence->setIntegration($integration);
$newToken = $this->tokenPersistence->restoreToken($tokenFromApi);
$this->assertSame(0, $newToken->getExpiresAt());
$this->assertFalse($newToken->isExpired());
}
public function testRestoreTokenExpiresTokenIfApplicable(): void
{
$accessToken = 'access_token';
$refreshToken = 'refresh_token';
$expiresAt = time() - 100;
$apiKeys = [
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
];
$factory = new RawTokenFactory();
$tokenFromApi = $factory([
'access_token' => $accessToken,
'refresh_token' => $refreshToken,
'expires_at' => $expiresAt,
]);
$integration = $this->createMock(Integration::class);
$integration->expects($this->once())
->method('getApiKeys')
->willReturn($apiKeys);
$this->tokenPersistence->setIntegration($integration);
$newToken = $this->tokenPersistence->restoreToken($tokenFromApi);
$this->assertSame($apiKeys['expires_at'], $newToken->getExpiresAt());
$this->assertTrue($newToken->isExpired());
}
}

View File

@@ -0,0 +1,102 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Command;
use Mautic\CoreBundle\Test\IsolatedTestTrait;
use Mautic\IntegrationsBundle\Command\SyncCommand;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncService\SyncServiceInterface;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Tester\CommandTester;
class SyncCommandTest extends TestCase
{
use IsolatedTestTrait;
private const INTEGRATION_NAME = 'Test';
/**
* @var SyncServiceInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $syncService;
private CommandTester $commandTester;
public function setUp(): void
{
parent::setUp();
$this->syncService = $this->createMock(SyncServiceInterface::class);
$application = new Application();
$application->addCommand(new SyncCommand($this->syncService));
// env is global option. Must be defined.
$application->getDefinition()->addOption(
new InputOption(
'--env',
'-e',
InputOption::VALUE_OPTIONAL,
'The environment to operate in.',
'DEV'
)
);
$this->commandTester = new CommandTester(
$application->find(SyncCommand::NAME)
);
}
public function testExecuteWithoutIntetrationName(): void
{
$this->assertSame(1, $this->commandTester->execute([]));
}
#[\PHPUnit\Framework\Attributes\PreserveGlobalState(false)]
#[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
public function testExecuteWithSomeOptions(): void
{
$this->syncService->expects($this->once())
->method('processIntegrationSync')
->with($this->callback(function (InputOptionsDAO $inputOptionsDAO) {
$this->assertSame(self::INTEGRATION_NAME, $inputOptionsDAO->getIntegration());
$this->assertSame(['123', '345'], $inputOptionsDAO->getMauticObjectIds()->getObjectIdsFor(Contact::NAME));
$this->assertNull($inputOptionsDAO->getIntegrationObjectIds());
$this->assertTrue($inputOptionsDAO->pullIsEnabled());
$this->assertFalse($inputOptionsDAO->pushIsEnabled());
return true;
}));
$code = $this->commandTester->execute([
'integration' => self::INTEGRATION_NAME,
'--disable-push' => true,
'--mautic-object-id' => ['contact:123', 'contact:345'],
]);
$this->assertSame(0, $code);
}
#[\PHPUnit\Framework\Attributes\PreserveGlobalState(false)]
#[\PHPUnit\Framework\Attributes\RunInSeparateProcess]
public function testExecuteWhenSyncThrowsException(): void
{
$this->syncService->expects($this->once())
->method('processIntegrationSync')
->with($this->callback(function (InputOptionsDAO $inputOptionsDAO) {
$this->assertSame(self::INTEGRATION_NAME, $inputOptionsDAO->getIntegration());
return true;
}))
->will($this->throwException(new \Exception()));
$code = $this->commandTester->execute(['integration' => self::INTEGRATION_NAME]);
$this->assertSame(1, $code);
}
}

View File

@@ -0,0 +1,28 @@
<?php
declare(strict_types=1);
use Mautic\IntegrationsBundle\DTO\Note;
use PHPUnit\Framework\TestCase;
final class NoteTest extends TestCase
{
public function testGetterFunctions(): void
{
$note = 'This is note';
$type = Note::TYPE_WARNING;
$noteObject = new Note($note, $type);
$this->assertSame($note, $noteObject->getNote());
$this->assertSame($type, $noteObject->getType());
}
public function testGetterFunctionsThrowsException(): void
{
$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage(sprintf('Type value can be either "%s" or "%s".', Note::TYPE_INFO, Note::TYPE_WARNING));
$noteObject = new Note('Notes', 'randomType');
}
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Mautic\IntegrationsBundle\Tests\Unit\Entity;
use Doctrine\DBAL\Query\QueryBuilder;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use Mautic\IntegrationsBundle\Entity\FieldChange;
use Mautic\IntegrationsBundle\Entity\FieldChangeRepository;
use Mautic\LeadBundle\Entity\Company;
use PHPUnit\Framework\TestCase;
class FieldChangeRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
private FieldChangeRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->configureRepository(FieldChange::class);
$this->connection->method('createQueryBuilder')->willReturnCallback(fn () => new QueryBuilder($this->connection));
}
public function testWhereQueryPartForFindingChangesForSingleObject(): void
{
$integration = 'test';
$objectType = 'foobar';
$objectId = 5;
$this->connection->expects($this->once())
->method('executeQuery')
->with(
'SELECT * FROM '.MAUTIC_TABLE_PREFIX.'sync_object_field_change_report f WHERE (f.integration = :integration) AND (f.object_type = :objectType) AND (f.object_id = :objectId) ORDER BY f.modified_at ASC',
[
'integration' => $integration,
'objectType' => $objectType,
'objectId' => $objectId,
]
);
$this->repository->findChangesForObject($integration, $objectType, $objectId);
}
public function testDeleteEntitiesForObject(): void
{
$this->connection->expects($this->once())
->method('executeStatement')
->with(
'DELETE FROM '.MAUTIC_TABLE_PREFIX.'sync_object_field_change_report WHERE (object_type = :objectType) AND (object_id = :objectId)',
[
'objectType' => Company::class,
'objectId' => 123,
]
)->willReturn(1);
$this->repository->deleteEntitiesForObject(123, Company::class);
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Entity;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\AbstractQuery;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Entity\ObjectMappingRepository;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
final class ObjectMappingRepositoryTest extends TestCase
{
use RepositoryConfiguratorTrait;
/**
* @var MockObject&AbstractQuery<mixed>
*/
private MockObject $query;
private ObjectMappingRepository $repository;
protected function setUp(): void
{
parent::setUp();
$this->repository = $this->configureRepository(ObjectMapping::class);
$this->entityManager->method('createQueryBuilder')->willReturnCallback(fn () => new QueryBuilder($this->entityManager));
// This is terrible, but the Query class is final and AbstractQuery doesn't have some methods used.
$this->query = $this->getMockBuilder(Query::class)
->disableOriginalConstructor()
->onlyMethods(['setParameters', 'getSingleResult', 'getSQL', '_doExecute', 'setFirstResult', 'setMaxResults'])
->getMock();
$this->query->expects($this->once())
->method('setFirstResult')
->willReturnSelf();
$this->query->expects($this->once())
->method('setMaxResults')
->willReturnSelf();
}
public function testDeleteEntitiesForObject(): void
{
$this->entityManager->expects($this->once())
->method('createQuery')
->with('DELETE Mautic\IntegrationsBundle\Entity\ObjectMapping m WHERE m.internalObjectName = :internalObject AND m.internalObjectId = :internalObjectId')
->willReturn($this->query);
$this->query->expects($this->once())
->method('setParameters')
->with($this->callback(function (ArrayCollection $collection) {
/** @var Parameter $parameter */
$parameter = $collection[0];
$this->assertSame('internalObject', $parameter->getName());
$this->assertSame('company', $parameter->getValue());
/** @var Parameter $parameter */
$parameter = $collection[1];
$this->assertSame('internalObjectId', $parameter->getName());
$this->assertSame(123, $parameter->getValue());
return true;
}))
->willReturnSelf();
// // Stopping early to avoid Mocking hell. We have what we needed.
$this->query->expects($this->once())
->method('_doExecute')
->willReturn(0);
$this->repository->deleteEntitiesForObject(123, 'company');
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Entity;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use PHPUnit\Framework\TestCase;
class ObjectMappingTest extends TestCase
{
private \DateTime $dateCreated;
public function setUp(): void
{
$this->dateCreated = new \DateTime();
parent::setUp();
}
public function testConstruct(): void
{
$objectMapping = new ObjectMapping($this->dateCreated);
$this->assertEquals($this->dateCreated, $objectMapping->getDateCreated());
}
public function testSetAndGetIntegrationReferenceId(): void
{
$objectMapping = new ObjectMapping($this->dateCreated);
$objectMapping->setIntegrationReferenceId('ref');
$this->assertEquals('ref', $objectMapping->getIntegrationReferenceId());
}
public function testLoadMetadata(): void
{
$metadata = new \Doctrine\ORM\Mapping\ClassMetadata(ObjectMapping::class);
ObjectMapping::loadMetadata($metadata);
$expectedFieldNames = [
'id',
'dateCreated',
'integration',
'internalObjectName',
'internalObjectId',
'integrationObjectName',
'integrationObjectId',
'lastSyncDate',
'internalStorage',
'isDeleted',
'integrationReferenceId',
];
$this->assertEquals($expectedFieldNames, $metadata->getFieldNames());
$referenceIdMapping = $metadata->table['indexes']['integration_reference'];
$this->assertEquals(
[
'integration',
'integration_object_name',
'integration_reference_id',
'integration_object_id',
],
$referenceIdMapping['columns'],
'Required index is not being created.'
);
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Event;
use Mautic\IntegrationsBundle\Event\CompletedSyncIterationEvent;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\OrderResultsDAO;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class CompletedSyncIterationEventTest extends TestCase
{
public function testGetters(): void
{
$mappingManual = new MappingManualDAO('foobar');
$orderResults = new OrderResultsDAO([], [], [], []);
$iteration = 1;
$inputOptions = new InputOptionsDAO(['integration' => 'foobar']);
$event = new CompletedSyncIterationEvent($orderResults, $iteration, $inputOptions, $mappingManual);
Assert::assertSame($mappingManual->getIntegration(), $event->getIntegration());
Assert::assertSame($orderResults, $event->getOrderResults());
Assert::assertSame($iteration, $event->getIteration());
Assert::assertSame($inputOptions, $event->getInputOptions());
Assert::assertSame($mappingManual, $event->getMappingManual());
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Event;
use Mautic\IntegrationsBundle\Event\ConfigSaveEvent;
use Mautic\PluginBundle\Entity\Integration;
use PHPUnit\Framework\TestCase;
class ConfigSaveEventTest extends TestCase
{
public function testGetters(): void
{
$name = 'name';
$integration = $this->createMock(Integration::class);
$event = new ConfigSaveEvent($integration);
$integration->expects(self::once())
->method('getName')
->willReturn($name);
self::assertSame($integration, $event->getIntegrationConfiguration());
self::assertSame($name, $event->getIntegration());
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Event;
use Mautic\IntegrationsBundle\Event\KeysSaveEvent;
use Mautic\PluginBundle\Entity\Integration;
use PHPUnit\Framework\TestCase;
class KeysSaveEventTest extends TestCase
{
public function testGetters(): void
{
$integration = $this->createMock(Integration::class);
$keys = ['apikey' => 'test'];
$integration->expects(self::once())
->method('getApiKeys')
->willReturn($keys);
$event = new KeysSaveEvent($integration, $keys);
self::assertSame($integration, $event->getIntegrationConfiguration());
self::assertSame($keys, $event->getOldKeys());
self::assertSame($keys, $event->getNewKeys());
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Event;
use Mautic\IntegrationsBundle\Event\MauticSyncFieldsLoadEvent;
use PHPUnit\Framework\TestCase;
class MauticSyncFieldsLoadEventTest extends TestCase
{
public function testWorkflow(): void
{
$objectName = 'object';
$fields = [
'fieldKey' => 'fieldName',
];
$newFieldKey = 'newFieldKey';
$newFieldValue = 'newFieldValue';
$event = new MauticSyncFieldsLoadEvent($objectName, $fields);
$this->assertSame($objectName, $event->getObjectName());
$this->assertSame($fields, $event->getFields());
$event->addField($newFieldKey, $newFieldValue);
$this->assertSame(
array_merge($fields, [$newFieldKey => $newFieldValue]),
$event->getFields()
);
}
}

View File

@@ -0,0 +1,360 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\EventListener;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Event\InternalObjectCreateEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectFindByIdEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectOwnerEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectRouteEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectUpdateEvent;
use Mautic\IntegrationsBundle\EventListener\CompanyObjectSubscriber;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\DateRange;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\UpdatedObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\CompanyObjectHelper;
use Mautic\LeadBundle\Entity\Company as CompanyEntity;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Router;
class CompanyObjectSubscriberTest extends TestCase
{
private CompanyObjectHelper|MockObject $companyObjectHelper;
private Router|MockObject $router;
private CompanyObjectSubscriber $subscriber;
public function setUp(): void
{
parent::setUp();
$this->companyObjectHelper = $this->createMock(CompanyObjectHelper::class);
$this->router = $this->createMock(Router::class);
$this->subscriber = new CompanyObjectSubscriber(
$this->companyObjectHelper,
$this->router
);
}
public function testGetSubscribedEvents(): void
{
$this->assertEquals(
[
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS => ['collectInternalObjects', 0],
IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS => ['updateCompanies', 0],
IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS => ['createCompanies', 0],
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS => [
['findCompaniesByIds', 0],
['findCompaniesByDateRange', 0],
['findCompaniesByFieldValues', 0],
],
IntegrationEvents::INTEGRATION_FIND_OWNER_IDS => ['findOwnerIdsForCompanies', 0],
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE => ['buildCompanyRoute', 0],
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD => ['findCompanyById', 0],
],
CompanyObjectSubscriber::getSubscribedEvents()
);
}
public function testCollectInternalObjects(): void
{
$event = new InternalObjectEvent();
$this->subscriber->collectInternalObjects($event);
$this->assertCount(1, $event->getObjects());
$this->assertInstanceOf(
Company::class,
$event->getObjects()[0]
);
}
public function testUpdateCompaniesWithWrongObject(): void
{
$event = new InternalObjectUpdateEvent(new Contact(), [], []);
$this->companyObjectHelper->expects($this->never())
->method('update');
$this->subscriber->updateCompanies($event);
$this->assertSame([], $event->getUpdatedObjectMappings());
}
public function testUpdateCompaniesWithRightObject(): void
{
$objectChangeDAO = new ObjectChangeDAO('integration', 'object', 'objectId', 'mappedObject', 'mappedId');
$event = new InternalObjectUpdateEvent(new Company(), [123], [$objectChangeDAO]);
$objectMapping = $this->createMock(UpdatedObjectMappingDAO::class);
$this->companyObjectHelper->expects($this->once())
->method('update')
->with([123], [$objectChangeDAO])
->willReturn([$objectMapping]);
$this->subscriber->updateCompanies($event);
$this->assertSame([$objectMapping], $event->getUpdatedObjectMappings());
}
public function testCreateCompaniesWithWrongObject(): void
{
$event = new InternalObjectCreateEvent(new Contact(), []);
$this->companyObjectHelper->expects($this->never())
->method('create');
$this->subscriber->createCompanies($event);
$this->assertSame([], $event->getObjectMappings());
}
public function testCreateCompaniesWithRightObject(): void
{
$event = new InternalObjectCreateEvent(new Company(), [['somefield' => 'somevalue']]);
$objectMapping = $this->createMock(ObjectMapping::class);
$this->companyObjectHelper->expects($this->once())
->method('create')
->with([['somefield' => 'somevalue']])
->willReturn([$objectMapping]);
$this->subscriber->createCompanies($event);
$this->assertSame([$objectMapping], $event->getObjectMappings());
}
public function testFindCompaniesByIdsWithWrongObject(): void
{
$event = new InternalObjectFindEvent(new Contact());
$this->companyObjectHelper->expects($this->never())
->method('findObjectsByIds');
$this->subscriber->findCompaniesByIds($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindCompaniesByIdsWithNoIds(): void
{
$event = new InternalObjectFindEvent(new Company());
$this->companyObjectHelper->expects($this->never())
->method('findObjectsByIds');
$this->subscriber->findCompaniesByIds($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindCompaniesByIdsWithRightObject(): void
{
$event = new InternalObjectFindEvent(new Company());
$event->setIds([123]);
$this->companyObjectHelper->expects($this->once())
->method('findObjectsByIds')
->with([123])
->willReturn([['object_1']]);
$this->subscriber->findCompaniesByIds($event);
$this->assertSame([['object_1']], $event->getFoundObjects());
}
public function testFindCompaniesByDateRangeWithWrongObject(): void
{
$event = new InternalObjectFindEvent(new Contact());
$this->companyObjectHelper->expects($this->never())
->method('findObjectsBetweenDates');
$this->subscriber->findCompaniesByDateRange($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindCompaniesByDateRangeWithNoDateRange(): void
{
$event = new InternalObjectFindEvent(new Company());
$this->companyObjectHelper->expects($this->never())
->method('findObjectsBetweenDates');
$this->subscriber->findCompaniesByDateRange($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindCompaniesByDateRangeWithRightObject(): void
{
$event = new InternalObjectFindEvent(new Company());
$fromDate = new \DateTimeImmutable();
$toDate = new \DateTimeImmutable();
$dateRange = new DateRange($fromDate, $toDate);
$start = 0;
$limit = 10;
$event->setDateRange($dateRange);
$event->setStart($start);
$event->setLimit($limit);
$this->companyObjectHelper->expects($this->once())
->method('findObjectsBetweenDates')
->with(
$fromDate,
$toDate,
$start,
$limit
)
->willReturn([['object_1']]);
$this->subscriber->findCompaniesByDateRange($event);
$this->assertSame([['object_1']], $event->getFoundObjects());
}
public function testFindCompaniesByFieldValuesWithWrongObject(): void
{
$event = new InternalObjectFindEvent(new Contact());
$this->companyObjectHelper->expects($this->never())
->method('findObjectsByFieldValues');
$this->subscriber->findCompaniesByFieldValues($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindCompaniesByFieldValuesWithNoIds(): void
{
$event = new InternalObjectFindEvent(new Company());
$this->companyObjectHelper->expects($this->never())
->method('findObjectsByFieldValues');
$this->subscriber->findCompaniesByFieldValues($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindCompaniesByFieldValuesWithRightObject(): void
{
$event = new InternalObjectFindEvent(new Company());
$event->setFieldValues(['field_a' => 123]);
$this->companyObjectHelper->expects($this->once())
->method('findObjectsByFieldValues')
->with(['field_a' => 123])
->willReturn([['object_1']]);
$this->subscriber->findCompaniesByFieldValues($event);
$this->assertSame([['object_1']], $event->getFoundObjects());
}
public function testFindOwnerIdsForCompaniesWithWrongObject(): void
{
$event = new InternalObjectOwnerEvent(new Contact(), []);
$this->companyObjectHelper->expects($this->never())
->method('findOwnerIds');
$this->subscriber->findOwnerIdsForCompanies($event);
$this->assertSame([], $event->getOwners());
}
public function testFindOwnerIdsForCompaniesWithRightObject(): void
{
$event = new InternalObjectOwnerEvent(new Company(), [567]);
$this->companyObjectHelper->expects($this->once())
->method('findOwnerIds')
->with([567])
->willReturn([['object_1']]);
$this->subscriber->findOwnerIdsForCompanies($event);
$this->assertSame([['object_1']], $event->getOwners());
}
public function testBuildCompanyRouteWithWrongObject(): void
{
$event = new InternalObjectRouteEvent(new Contact(), 123);
$this->router->expects($this->never())
->method('generate');
$this->subscriber->buildCompanyRoute($event);
$this->assertNull($event->getRoute());
}
public function testBuildCompanyRouteWithRightObject(): void
{
$event = new InternalObjectRouteEvent(new Company(), 123);
$this->router->expects($this->once())
->method('generate')
->with(
'mautic_company_action',
[
'objectAction' => 'view',
'objectId' => 123,
]
)
->willReturn('some/route');
$this->subscriber->buildCompanyRoute($event);
$this->assertSame('some/route', $event->getRoute());
}
public function testFindCompanyById(): void
{
$event = new InternalObjectFindByIdEvent(new Company());
$event->setId(1);
$companyObj = $this->createMock(CompanyEntity::class);
$this->companyObjectHelper->expects($this->once())
->method('findObjectById')
->with(1)
->willReturn($companyObj);
$this->subscriber->findCompanyById($event);
self::assertSame($companyObj, $event->getEntity());
}
public function testFindCompanyByIdWithNoIdSet(): void
{
$event = new InternalObjectFindByIdEvent(new Company());
$this->companyObjectHelper->expects($this->never())
->method('findObjectById');
$this->subscriber->findCompanyById($event);
self::assertNull($event->getEntity());
}
public function testFindCompanyByIdWithNoCompany(): void
{
$event = new InternalObjectFindByIdEvent(new Company());
$event->setId(1);
$this->companyObjectHelper->expects($this->once())
->method('findObjectById')
->with(1)
->willReturn(null);
$this->subscriber->findCompanyById($event);
self::assertNull($event->getEntity());
}
}

View File

@@ -0,0 +1,375 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\EventListener;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Event\InternalObjectCreateEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectFindByIdEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectOwnerEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectRouteEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectUpdateEvent;
use Mautic\IntegrationsBundle\EventListener\ContactObjectSubscriber;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\DateRange;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\UpdatedObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\ContactObjectHelper;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Exception\ImportFailedException;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Router;
class ContactObjectSubscriberTest extends TestCase
{
/**
* @var ContactObjectHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $contactObjectHelper;
/**
* @var Router|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $router;
private ContactObjectSubscriber $subscriber;
public function setUp(): void
{
parent::setUp();
$this->contactObjectHelper = $this->createMock(ContactObjectHelper::class);
$this->router = $this->createMock(Router::class);
$this->subscriber = new ContactObjectSubscriber(
$this->contactObjectHelper,
$this->router
);
}
public function testGetSubscribedEvents(): void
{
$this->assertEquals(
[
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS => ['collectInternalObjects', 0],
IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS => ['updateContacts', 0],
IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS => ['createContacts', 0],
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS => [
['findContactsByIds', 0],
['findContactsByDateRange', 0],
['findContactsByFieldValues', 0],
],
IntegrationEvents::INTEGRATION_FIND_OWNER_IDS => ['findOwnerIdsForContacts', 0],
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE => ['buildContactRoute', 0],
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD => ['findContactById', 0],
],
ContactObjectSubscriber::getSubscribedEvents()
);
}
public function testCollectInternalObjects(): void
{
$event = new InternalObjectEvent();
$this->subscriber->collectInternalObjects($event);
$this->assertCount(1, $event->getObjects());
$this->assertInstanceOf(
Contact::class,
$event->getObjects()[0]
);
}
public function testUpdateContactsWithWrongObject(): void
{
$event = new InternalObjectUpdateEvent(new Company(), [], []);
$this->contactObjectHelper->expects($this->never())
->method('update');
$this->subscriber->updateContacts($event);
$this->assertSame([], $event->getUpdatedObjectMappings());
}
public function testUpdateContactsWithRightObject(): void
{
$objectChangeDAO = new ObjectChangeDAO('integration', 'object', 'objectId', 'mappedObject', 'mappedId');
$event = new InternalObjectUpdateEvent(new Contact(), [123], [$objectChangeDAO]);
$objectMapping = $this->createMock(UpdatedObjectMappingDAO::class);
$this->contactObjectHelper->expects($this->once())
->method('update')
->with([123], [$objectChangeDAO])
->willReturn([$objectMapping]);
$this->subscriber->updateContacts($event);
$this->assertSame([$objectMapping], $event->getUpdatedObjectMappings());
}
public function testCreateContactsWithWrongObject(): void
{
$event = new InternalObjectCreateEvent(new Company(), []);
$this->contactObjectHelper->expects($this->never())
->method('create');
$this->subscriber->createContacts($event);
$this->assertSame([], $event->getObjectMappings());
}
public function testCreateContactsWithRightObject(): void
{
$event = new InternalObjectCreateEvent(new Contact(), [['somefield' => 'somevalue']]);
$objectMapping = $this->createMock(ObjectMapping::class);
$this->contactObjectHelper->expects($this->once())
->method('create')
->with([['somefield' => 'somevalue']])
->willReturn([$objectMapping]);
$this->subscriber->createContacts($event);
$this->assertSame([$objectMapping], $event->getObjectMappings());
}
public function testFindContactsByIdsWithWrongObject(): void
{
$event = new InternalObjectFindEvent(new Company());
$this->contactObjectHelper->expects($this->never())
->method('findObjectsByIds');
$this->subscriber->findContactsByIds($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindContactsByIdsWithNoIds(): void
{
$event = new InternalObjectFindEvent(new Contact());
$this->contactObjectHelper->expects($this->never())
->method('findObjectsByIds');
$this->subscriber->findContactsByIds($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindContactsByIdsWithRightObject(): void
{
$event = new InternalObjectFindEvent(new Contact());
$event->setIds([123]);
$this->contactObjectHelper->expects($this->once())
->method('findObjectsByIds')
->with([123])
->willReturn([['object_1']]);
$this->subscriber->findContactsByIds($event);
$this->assertSame([['object_1']], $event->getFoundObjects());
}
public function testFindContactsByDateRangeWithWrongObject(): void
{
$event = new InternalObjectFindEvent(new Company());
$this->contactObjectHelper->expects($this->never())
->method('findObjectsBetweenDates');
$this->subscriber->findContactsByDateRange($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindContactsByDateRangeWithNoDateRange(): void
{
$event = new InternalObjectFindEvent(new Contact());
$this->contactObjectHelper->expects($this->never())
->method('findObjectsBetweenDates');
$this->subscriber->findContactsByDateRange($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindContactsByDateRangeWithRightObject(): void
{
$event = new InternalObjectFindEvent(new Contact());
$fromDate = new \DateTimeImmutable();
$toDate = new \DateTimeImmutable();
$dateRange = new DateRange($fromDate, $toDate);
$start = 0;
$limit = 10;
$event->setDateRange($dateRange);
$event->setStart($start);
$event->setLimit($limit);
$this->contactObjectHelper->expects($this->once())
->method('findObjectsBetweenDates')
->with(
$fromDate,
$toDate,
$start,
$limit
)
->willReturn([['object_1']]);
$this->subscriber->findContactsByDateRange($event);
$this->assertSame([['object_1']], $event->getFoundObjects());
}
public function testFindContactsByFieldValuesWithWrongObject(): void
{
$event = new InternalObjectFindEvent(new Company());
$this->contactObjectHelper->expects($this->never())
->method('findObjectsByFieldValues');
$this->subscriber->findContactsByFieldValues($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindContactsByFieldValuesWithNoIds(): void
{
$event = new InternalObjectFindEvent(new Contact());
$this->contactObjectHelper->expects($this->never())
->method('findObjectsByFieldValues');
$this->subscriber->findContactsByFieldValues($event);
$this->assertSame([], $event->getFoundObjects());
}
public function testFindContactsByFieldValuesWithRightObject(): void
{
$event = new InternalObjectFindEvent(new Contact());
$event->setFieldValues(['field_a' => 123]);
$this->contactObjectHelper->expects($this->once())
->method('findObjectsByFieldValues')
->with(['field_a' => 123])
->willReturn([['object_1']]);
$this->subscriber->findContactsByFieldValues($event);
$this->assertSame([['object_1']], $event->getFoundObjects());
}
public function testFindOwnerIdsForContactsWithWrongObject(): void
{
$event = new InternalObjectOwnerEvent(new Company(), []);
$this->contactObjectHelper->expects($this->never())
->method('findOwnerIds');
$this->subscriber->findOwnerIdsForContacts($event);
$this->assertSame([], $event->getOwners());
}
public function testFindOwnerIdsForContactsWithRightObject(): void
{
$event = new InternalObjectOwnerEvent(new Contact(), [567]);
$this->contactObjectHelper->expects($this->once())
->method('findOwnerIds')
->with([567])
->willReturn([['object_1']]);
$this->subscriber->findOwnerIdsForContacts($event);
$this->assertSame([['object_1']], $event->getOwners());
}
public function testBuildContactRouteWithWrongObject(): void
{
$event = new InternalObjectRouteEvent(new Company(), 123);
$this->router->expects($this->never())
->method('generate');
$this->subscriber->buildContactRoute($event);
$this->assertNull($event->getRoute());
}
public function testBuildContactRouteWithRightObject(): void
{
$event = new InternalObjectRouteEvent(new Contact(), 123);
$this->router->expects($this->once())
->method('generate')
->with(
'mautic_contact_action',
[
'objectAction' => 'view',
'objectId' => 123,
]
)
->willReturn('some/route');
$this->subscriber->buildContactRoute($event);
$this->assertSame('some/route', $event->getRoute());
}
/**
* @throws ImportFailedException
*/
public function testFindContactById(): void
{
$event = new InternalObjectFindByIdEvent(new Contact());
$event->setId(1);
$contactObj = $this->createMock(Lead::class);
$this->contactObjectHelper->expects($this->once())
->method('findObjectById')
->with(1)
->willReturn($contactObj);
$this->subscriber->findContactById($event);
self::assertSame($contactObj, $event->getEntity());
}
/**
* @throws ImportFailedException
*/
public function testFindContactByIdWithNoIdSet(): void
{
$event = new InternalObjectFindByIdEvent(new Contact());
$this->contactObjectHelper->expects($this->never())
->method('findObjectById');
$this->subscriber->findContactById($event);
self::assertNull($event->getEntity());
}
/**
* @throws ImportFailedException
*/
public function testFindContactByIdWithNoContact(): void
{
$event = new InternalObjectFindByIdEvent(new Contact());
$event->setId(1);
$this->contactObjectHelper->expects($this->once())
->method('findObjectById')
->with(1)
->willReturn(null);
$this->subscriber->findContactById($event);
self::assertNull($event->getEntity());
}
}

View File

@@ -0,0 +1,512 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\EventListener;
use Mautic\IntegrationsBundle\Entity\FieldChangeRepository;
use Mautic\IntegrationsBundle\Entity\ObjectMappingRepository;
use Mautic\IntegrationsBundle\EventListener\LeadSubscriber;
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
use Mautic\IntegrationsBundle\Helper\SyncIntegrationsHelper;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\Value\EncodedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\IntegrationsBundle\Sync\VariableExpresser\VariableExpresserHelperInterface;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Event\CompanyEvent;
use Mautic\LeadBundle\Event\LeadEvent;
use Mautic\LeadBundle\LeadEvents;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class LeadSubscriberTest extends TestCase
{
/**
* @var MockObject|FieldChangeRepository
*/
private MockObject $fieldChangeRepository;
/**
* @var MockObject|ObjectMappingRepository
*/
private MockObject $objectMappingRepository;
/**
* @var MockObject|VariableExpresserHelperInterface
*/
private MockObject $variableExpresserHelper;
/**
* @var MockObject|SyncIntegrationsHelper
*/
private MockObject $syncIntegrationsHelper;
/**
* @var MockObject|CompanyEvent
*/
private MockObject $companyEvent;
private LeadSubscriber $subscriber;
/**
* @var MockObject|EventDispatcherInterface
*/
private MockObject $eventDispatcherInterfaceMock;
public function setUp(): void
{
parent::setUp();
$this->fieldChangeRepository = $this->createMock(FieldChangeRepository::class);
$this->objectMappingRepository = $this->createMock(ObjectMappingRepository::class);
$this->variableExpresserHelper = $this->createMock(VariableExpresserHelperInterface::class);
$this->syncIntegrationsHelper = $this->createMock(SyncIntegrationsHelper::class);
$this->companyEvent = $this->createMock(CompanyEvent::class);
$this->eventDispatcherInterfaceMock = $this->createMock(EventDispatcherInterface::class);
$this->subscriber = new LeadSubscriber(
$this->fieldChangeRepository,
$this->objectMappingRepository,
$this->variableExpresserHelper,
$this->syncIntegrationsHelper,
$this->eventDispatcherInterfaceMock
);
}
public function testGetSubscribedEvents(): void
{
Assert::assertEquals(
[
LeadEvents::LEAD_POST_SAVE => ['onLeadPostSave', 0],
LeadEvents::LEAD_POST_DELETE => ['onLeadPostDelete', 255],
LeadEvents::COMPANY_POST_SAVE => ['onCompanyPostSave', 0],
LeadEvents::COMPANY_POST_DELETE => ['onCompanyPostDelete', 255],
LeadEvents::LEAD_COMPANY_CHANGE => ['onLeadCompanyChange', 128],
],
LeadSubscriber::getSubscribedEvents()
);
}
public function testOnLeadPostSaveAnonymousLead(): void
{
$lead = $this->createMock(Lead::class);
$lead->expects($this->once())
->method('isAnonymous')
->willReturn(true);
$lead->expects($this->never())
->method('getChanges');
$this->syncIntegrationsHelper->expects($this->never())
->method('hasObjectSyncEnabled');
$this->subscriber->onLeadPostSave(new LeadEvent($lead));
}
public function testOnLeadPostSaveLeadObjectSyncNotEnabled(): void
{
$lead = $this->createMock(Lead::class);
$lead->expects($this->once())
->method('isAnonymous')
->willReturn(false);
$lead->expects($this->never())
->method('getChanges');
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(Contact::NAME)
->willReturn(false);
$this->subscriber->onLeadPostSave(new LeadEvent($lead));
}
public function testOnLeadPostSaveNoAction(): void
{
$fieldChanges = [];
$lead = $this->createMock(Lead::class);
$lead->expects($this->once())
->method('isAnonymous')
->willReturn(false);
$lead->expects($this->once())
->method('getChanges')
->willReturn($fieldChanges);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(Contact::NAME)
->willReturn(true);
$this->subscriber->onLeadPostSave(new LeadEvent($lead));
}
/**
* @throws IntegrationNotFoundException
* @throws ObjectNotFoundException
*/
public function testOnLeadPostSaveRecordChanges(): void
{
$fieldName = 'fieldName';
$oldValue = 'oldValue';
$newValue = 'newValue';
$fieldChanges = [
'fields' => [
$fieldName => [
$oldValue,
$newValue,
],
],
];
$objectId = 1;
$lead = $this->createLeadMock($fieldChanges, $objectId);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(Contact::NAME)
->willReturn(true);
$this->handleRecordFieldChanges($fieldChanges['fields'], $objectId, Lead::class);
$this->eventDispatcherInterfaceMock
->method('hasListeners')
->with(IntegrationEvents::INTEGRATION_BEFORE_CONTACT_FIELD_CHANGES)
->willReturn(true);
$this->subscriber->onLeadPostSave(new LeadEvent($lead));
}
/**
* @throws IntegrationNotFoundException
* @throws ObjectNotFoundException
*/
public function testOnLeadPostSaveRecordChangesWithOwnerChange(): void
{
$newOwnerId = 5;
$fieldChanges = [
'owner' => [
2,
$newOwnerId,
],
];
$objectId = 1;
$lead = $this->createLeadMock($fieldChanges, $objectId);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(Contact::NAME)
->willReturn(true);
$fieldChanges['fields']['owner_id'] = $fieldChanges['owner'];
$this->handleRecordFieldChanges($fieldChanges['fields'], $objectId, Lead::class);
$this->eventDispatcherInterfaceMock
->method('hasListeners')
->with(IntegrationEvents::INTEGRATION_BEFORE_CONTACT_FIELD_CHANGES)
->willReturn(true);
$this->subscriber->onLeadPostSave(new LeadEvent($lead));
}
/**
* @throws IntegrationNotFoundException
* @throws ObjectNotFoundException
*/
public function testOnLeadPostSaveRecordChangesWithPointChange(): void
{
$newPointCount = 5;
$fieldChanges = [
'points' => [
2,
$newPointCount,
],
];
$objectId = 1;
$lead = $this->createLeadMock($fieldChanges, $objectId);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(Contact::NAME)
->willReturn(true);
$fieldChanges['fields']['points'] = $fieldChanges['points'];
$this->handleRecordFieldChanges($fieldChanges['fields'], $objectId, Lead::class);
$this->eventDispatcherInterfaceMock
->method('hasListeners')
->with(IntegrationEvents::INTEGRATION_BEFORE_CONTACT_FIELD_CHANGES)
->willReturn(true);
$this->subscriber->onLeadPostSave(new LeadEvent($lead));
}
public function testOnLeadPostDelete(): void
{
$deletedId = 5;
$lead = new Lead();
$lead->deletedId = $deletedId;
$lead->setEmail('john@doe.email');
$this->fieldChangeRepository->expects($this->once())
->method('deleteEntitiesForObject')
->with((int) $deletedId, Lead::class);
$this->objectMappingRepository->expects($this->once())
->method('deleteEntitiesForObject')
->with((int) $deletedId, MauticSyncDataExchange::OBJECT_CONTACT);
$this->subscriber->onLeadPostDelete(new LeadEvent($lead));
}
public function testOnLeadPostDeleteForAnonymousLeads(): void
{
$deletedId = 5;
$lead = new Lead();
$lead->deletedId = $deletedId;
$this->fieldChangeRepository->expects($this->never())
->method('deleteEntitiesForObject');
$this->objectMappingRepository->expects($this->never())
->method('deleteEntitiesForObject');
$this->subscriber->onLeadPostDelete(new LeadEvent($lead));
}
public function testOnCompanyPostSaveSyncNotEnabled(): void
{
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(MauticSyncDataExchange::OBJECT_COMPANY)
->willReturn(false);
$this->companyEvent->expects($this->never())
->method('getCompany');
$this->subscriber->onCompanyPostSave($this->companyEvent);
}
public function testOnCompanyPostSaveSyncNoAction(): void
{
$fieldChanges = [];
$company = $this->createCompanyMock($fieldChanges, 1);
$this->companyEvent->expects($this->once())
->method('getCompany')
->willReturn($company);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(MauticSyncDataExchange::OBJECT_COMPANY)
->willReturn(true);
$this->subscriber->onCompanyPostSave($this->companyEvent);
}
/**
* @throws IntegrationNotFoundException
* @throws ObjectNotFoundException
*/
public function testOnCompanyPostSaveSyncRecordChanges(): void
{
$fieldName = 'fieldName';
$oldValue = 'oldValue';
$newValue = 'newValue';
$fieldChanges = [
'fields' => [
$fieldName => [
$oldValue,
$newValue,
],
],
];
$objectId = 1;
$company = $this->createCompanyMock($fieldChanges, $objectId);
$this->companyEvent->expects($this->once())
->method('getCompany')
->willReturn($company);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(MauticSyncDataExchange::OBJECT_COMPANY)
->willReturn(true);
$this->handleRecordFieldChanges($fieldChanges['fields'], $objectId, Company::class);
$this->eventDispatcherInterfaceMock
->method('hasListeners')
->with(IntegrationEvents::INTEGRATION_BEFORE_COMPANY_FIELD_CHANGES)
->willReturn(true);
$this->subscriber->onCompanyPostSave($this->companyEvent);
}
/**
* @throws IntegrationNotFoundException
* @throws ObjectNotFoundException
*/
public function testOnCompanyPostSaveRecordChangesWithOwnerChange(): void
{
$newOwnerId = 5;
$fieldChanges = [
'owner' => [
2,
$newOwnerId,
],
];
$objectId = 1;
$company = $this->createCompanyMock($fieldChanges, $objectId);
$this->companyEvent->expects($this->once())
->method('getCompany')
->willReturn($company);
$this->syncIntegrationsHelper->expects($this->once())
->method('hasObjectSyncEnabled')
->with(MauticSyncDataExchange::OBJECT_COMPANY)
->willReturn(true);
$fieldChanges['fields']['owner_id'] = $fieldChanges['owner'];
$this->handleRecordFieldChanges($fieldChanges['fields'], $objectId, Company::class);
$this->eventDispatcherInterfaceMock
->method('hasListeners')
->with(IntegrationEvents::INTEGRATION_BEFORE_COMPANY_FIELD_CHANGES)
->willReturn(true);
$this->subscriber->onCompanyPostSave($this->companyEvent);
}
public function testOnCompanyPostDelete(): void
{
$deletedId = 5;
$lead = new Company();
$lead->deletedId = $deletedId;
$this->companyEvent->expects($this->exactly(2))
->method('getCompany')
->willReturn($lead);
$this->fieldChangeRepository->expects($this->once())
->method('deleteEntitiesForObject')
->with((int) $deletedId, Company::class);
$this->objectMappingRepository->expects($this->once())
->method('deleteEntitiesForObject')
->with((int) $deletedId, MauticSyncDataExchange::OBJECT_COMPANY);
$this->subscriber->onCompanyPostDelete($this->companyEvent);
}
private function handleRecordFieldChanges(array $fieldChanges, int $objectId, string $objectType): void
{
$integrationName = 'testIntegration';
$enabledIntegrations = [$integrationName];
$this->syncIntegrationsHelper->expects($this->any())
->method('getEnabledIntegrations')
->willReturn($enabledIntegrations);
$fieldNames = [];
$values = [];
$valueDAOs = [];
$i = 0;
foreach ($fieldChanges as $fieldName => [$oldValue, $newValue]) {
$values[] = [$newValue];
$valueDAOs[] = new EncodedValueDAO($objectType, (string) $newValue);
$fieldNames[] = $fieldName;
}
$matcher = $this->exactly(1);
$this->variableExpresserHelper->expects($matcher)->method('encodeVariable')
->willReturnCallback(function (...$parameters) use ($matcher, $values, $valueDAOs) {
$this->assertSame($values[$matcher->numberOfInvocations() - 1], $parameters);
return $valueDAOs[0];
});
$this->fieldChangeRepository->expects($this->once())
->method('deleteEntitiesForObjectByColumnName')
->with($objectId, $objectType, $fieldNames);
$this->fieldChangeRepository->expects($this->once())
->method('saveEntities');
$this->fieldChangeRepository->expects($this->once())
->method('detachEntities');
}
/**
* @param mixed[] $fieldChanges
*/
private function createLeadMock(array $fieldChanges, int $objectId): Lead
{
return new class($fieldChanges, $objectId) extends Lead {
/**
* @param mixed[] $fieldChanges
*/
public function __construct(
private array $fieldChanges,
private int $objectId,
) {
parent::__construct();
}
public function isAnonymous(): bool
{
return false;
}
public function getChanges($includePast = false): array
{
return $this->fieldChanges;
}
public function getId(): int
{
return $this->objectId;
}
};
}
/**
* @param mixed[] $fieldChanges
*/
private function createCompanyMock(array $fieldChanges, int $objectId): Company
{
return new class($fieldChanges, $objectId) extends Company {
/**
* @param mixed[] $fieldChanges
*/
public function __construct(
private array $fieldChanges,
private int $objectId,
) {
}
public function getChanges($includePast = false): array
{
return $this->fieldChanges;
}
public function getId(): int
{
return $this->objectId;
}
};
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Form\Type;
use Mautic\IntegrationsBundle\Exception\InvalidFormOptionException;
use Mautic\IntegrationsBundle\Form\Type\IntegrationSyncSettingsObjectFieldType;
use Mautic\IntegrationsBundle\Mapping\MappedFieldInfoInterface;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use PHPUnit\Framework\MockObject\MockObject;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\FormBuilderInterface;
final class IntegrationSyncSettingsObjectFieldTypeTest extends \PHPUnit\Framework\TestCase
{
/**
* @var MockObject|FormBuilderInterface
*/
private MockObject $formBuilder;
private IntegrationSyncSettingsObjectFieldType $form;
protected function setUp(): void
{
parent::setUp();
$this->formBuilder = $this->createMock(FormBuilderInterface::class);
$this->form = new IntegrationSyncSettingsObjectFieldType();
}
public function testBuildFormForWrongField(): void
{
$options = ['field' => 'unicorn'];
$this->expectException(InvalidFormOptionException::class);
$this->form->buildForm($this->formBuilder, $options);
}
public function testBuildFormForMappedField(): void
{
$field = $this->createMock(MappedFieldInfoInterface::class);
$options = [
'field' => $field,
'placeholder' => 'Placeholder ABC',
'object' => 'Object A',
'integration' => 'Integration A',
'mauticFields' => [
'mautic_field_a' => 'Mautic Field A',
'mautic_field_b' => 'Mautic Field B',
],
];
$field->method('showAsRequired')->willReturn(true);
$field->method('getName')->willReturn('Integration Field A');
$field->method('isBidirectionalSyncEnabled')->willReturn(false);
$field->method('isToIntegrationSyncEnabled')->willReturn(true);
$field->method('isToMauticSyncEnabled')->willReturn(true);
$matcher = $this->exactly(2);
$this->formBuilder->expects($matcher)
->method('add')->willReturnCallback(function (...$parameters) use ($matcher, $options) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('mappedField', $parameters[0]);
$this->assertSame(ChoiceType::class, $parameters[1]);
$this->assertSame([
'label' => false,
'choices' => [
'Mautic Field A' => 'mautic_field_a',
'Mautic Field B' => 'mautic_field_b',
],
'required' => true,
'placeholder' => '',
'error_bubbling' => false,
'attr' => [
'class' => 'form-control integration-mapped-field',
'data-placeholder' => $options['placeholder'],
'data-object' => $options['object'],
'data-integration' => $options['integration'],
'data-field' => 'Integration Field A',
],
], $parameters[2]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('syncDirection', $parameters[0]);
$this->assertSame(ChoiceType::class, $parameters[1]);
$this->assertSame([
'choices' => [
'mautic.integration.sync_direction_integration' => ObjectMappingDAO::SYNC_TO_INTEGRATION,
'mautic.integration.sync_direction_mautic' => ObjectMappingDAO::SYNC_TO_MAUTIC,
],
'label' => false,
'empty_data' => ObjectMappingDAO::SYNC_TO_INTEGRATION,
'attr' => [
'class' => 'integration-sync-direction',
'data-object' => 'Object A',
'data-integration' => 'Integration A',
'data-field' => 'Integration Field A',
],
], $parameters[2]);
}
return $this->formBuilder;
});
$this->form->buildForm($this->formBuilder, $options);
}
}

View File

@@ -0,0 +1,121 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Helper;
use Mautic\IntegrationsBundle\Exception\IntegrationNotFoundException;
use Mautic\IntegrationsBundle\Helper\BuilderIntegrationsHelper;
use Mautic\IntegrationsBundle\Helper\IntegrationsHelper;
use Mautic\IntegrationsBundle\Integration\Interfaces\BuilderInterface;
use Mautic\PluginBundle\Entity\Integration;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class BuilderIntegrationsHelperTest extends TestCase
{
/**
* @var IntegrationsHelper|MockObject
*/
private MockObject $integrationsHelper;
private BuilderIntegrationsHelper $builderIntegrationsHelper;
protected function setUp(): void
{
$this->integrationsHelper = $this->createMock(IntegrationsHelper::class);
$this->builderIntegrationsHelper = new BuilderIntegrationsHelper($this->integrationsHelper);
}
public function testBuilderNotFoundIfFeatureSupportedButNotEnabled(): void
{
$builder = $this->createMock(BuilderInterface::class);
$integration = new Integration();
$builder->expects($this->once())
->method('isSupported')
->with('page')
->willReturn(true);
$builder->expects($this->once())
->method('getIntegrationConfiguration')
->willReturn($integration);
$this->builderIntegrationsHelper->addIntegration($builder);
$this->expectException(IntegrationNotFoundException::class);
$this->builderIntegrationsHelper->getBuilder('page');
}
public function testBuilderNotFoundIfFeatureIsNotSupported(): void
{
$builder = $this->createMock(BuilderInterface::class);
$builder->expects($this->once())
->method('isSupported')
->with('page')
->willReturn(false);
$builder->expects($this->never())
->method('getIntegrationConfiguration');
$this->builderIntegrationsHelper->addIntegration($builder);
$this->expectException(IntegrationNotFoundException::class);
$this->builderIntegrationsHelper->getBuilder('page');
}
public function testBuilderFoundIfFeatureIsSupportedAndBuilderEnabled(): void
{
$builder = $this->createMock(BuilderInterface::class);
$integration = new Integration();
$integration->setIsPublished(true);
$builder->expects($this->once())
->method('isSupported')
->with('page')
->willReturn(true);
$builder->expects($this->once())
->method('getIntegrationConfiguration')
->willReturn($integration);
$this->builderIntegrationsHelper->addIntegration($builder);
$foundBuilder = $this->builderIntegrationsHelper->getBuilder('page');
Assert::assertSame($builder, $foundBuilder);
}
public function testBuilderNamesAreReturned(): void
{
$builder1 = $this->createMock(BuilderInterface::class);
$builder1->expects($this->exactly(2))
->method('getName')
->willReturn('builder1');
$builder1->expects($this->once())
->method('getDisplayName')
->willReturn('Builder One');
$this->builderIntegrationsHelper->addIntegration($builder1);
$builder2 = $this->createMock(BuilderInterface::class);
$builder2->expects($this->exactly(2))
->method('getName')
->willReturn('builder2');
$builder2->expects($this->once())
->method('getDisplayName')
->willReturn('Builder Two');
$this->builderIntegrationsHelper->addIntegration($builder2);
Assert::assertSame(
[
'builder1' => 'Builder One',
'builder2' => 'Builder Two',
],
$this->builderIntegrationsHelper->getBuilderNames()
);
}
}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Helper;
use Mautic\IntegrationsBundle\Helper\FieldFilterHelper;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
use Mautic\IntegrationsBundle\Mapping\MappedFieldInfoInterface;
use PHPUnit\Framework\TestCase;
class FieldFilterHelperTest extends TestCase
{
public function testFieldsFilteredByPage(): void
{
$integrationObject = $this->getIntegrationObject();
$fieldFilterHelper = new FieldFilterHelper($integrationObject);
$fieldFilterHelper->filterFieldsByPage('test', 2, 3);
$this->assertEquals(5, $fieldFilterHelper->getTotalFieldCount());
$filteredFields = $fieldFilterHelper->getFilteredFields();
$this->assertFalse(isset($filteredFields['field1']));
$this->assertFalse(isset($filteredFields['field2']));
$this->assertFalse(isset($filteredFields['field3']));
$this->assertTrue(isset($filteredFields['field4']));
$this->assertTrue(isset($filteredFields['field5']));
}
public function testFieldsFilteredByKeyword(): void
{
$integrationObject = $this->getIntegrationObject();
$fieldFilterHelper = new FieldFilterHelper($integrationObject);
$fieldFilterHelper->filterFieldsByKeyword('test', 'three', 1);
$this->assertEquals(1, $fieldFilterHelper->getTotalFieldCount());
$filteredFields = $fieldFilterHelper->getFilteredFields();
$this->assertFalse(isset($filteredFields['field1']));
$this->assertFalse(isset($filteredFields['field2']));
$this->assertTrue(isset($filteredFields['field3']));
$this->assertFalse(isset($filteredFields['field4']));
$this->assertFalse(isset($filteredFields['field5']));
}
public function testFieldsFilteredByKeywordAndPage(): void
{
$integrationObject = $this->getIntegrationObject();
$fieldFilterHelper = new FieldFilterHelper($integrationObject);
$fieldFilterHelper->filterFieldsByKeyword('test', 'field', 2, 3);
$this->assertEquals(5, $fieldFilterHelper->getTotalFieldCount());
$filteredFields = $fieldFilterHelper->getFilteredFields();
$this->assertFalse(isset($filteredFields['field1']));
$this->assertFalse(isset($filteredFields['field2']));
$this->assertFalse(isset($filteredFields['field3']));
$this->assertTrue(isset($filteredFields['field4']));
$this->assertTrue(isset($filteredFields['field5']));
}
/**
* @return \PHPUnit\Framework\MockObject\MockObject|ConfigFormSyncInterface
*/
private function getIntegrationObject()
{
$field1 = $this->createMock(MappedFieldInfoInterface::class);
$field1->method('getLabel')
->willReturn('field one');
$field2 = $this->createMock(MappedFieldInfoInterface::class);
$field2->method('getLabel')
->willReturn('field two');
$field3 = $this->createMock(MappedFieldInfoInterface::class);
$field3->method('getLabel')
->willReturn('field three');
$field4 = $this->createMock(MappedFieldInfoInterface::class);
$field4->method('getLabel')
->willReturn('field four');
$field5 = $this->createMock(MappedFieldInfoInterface::class);
$field5->method('getLabel')
->willReturn('field five');
$integrationObject = $this->createMock(ConfigFormSyncInterface::class);
$integrationObject->method('getAllFieldsForMapping')
->willReturn(
[
'field1' => $field1,
'field2' => $field2,
'field3' => $field3,
'field4' => $field4,
'field5' => $field5,
]
);
return $integrationObject;
}
}

View File

@@ -0,0 +1,483 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Helper;
use Mautic\IntegrationsBundle\Exception\InvalidFormOptionException;
use Mautic\IntegrationsBundle\Helper\FieldMergerHelper;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormSyncInterface;
use Mautic\IntegrationsBundle\Mapping\MappedFieldInfoInterface;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use PHPUnit\Framework\TestCase;
class FieldMergerHelperTest extends TestCase
{
public function testNonExistingFieldsAreRemoved(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject(true);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
'syncDirection' => 'bidirectional',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertFalse(isset($mergedFieldMappings['Lead']['field1']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testFieldUnsetIfMappingIsDeleted(): void
{
$fields = $this->getCurrentFieldMappings();
unset($fields['Lead']['field1']);
$integrationObject = $this->getIntegrationObject();
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertFalse(isset($mergedFieldMappings['Lead']['field1']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testCurrentFieldMappingsAreMerged(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject();
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
'syncDirection' => 'mautic',
],
];
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field1 = $integrationFields['field1'];
$field1->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(true);
$field1->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(true);
$field1->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertEquals($updatedFieldMappings['field1']['mappedField'], $mergedFieldMappings['Lead']['field1']['mappedField']);
$this->assertEquals($updatedFieldMappings['field1']['syncDirection'], $mergedFieldMappings['Lead']['field1']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testCurrentFieldMappingsAreMergedWithJustMappedFieldUpdated(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject();
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field4' => [
'mappedField' => 'mautic_test_field',
],
];
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field4 */
$field4 = $integrationFields['field4'];
$field4->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(false);
$field4->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(false);
$field4->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertEquals($updatedFieldMappings['field4']['mappedField'], $mergedFieldMappings['Lead']['field4']['mappedField']);
$this->assertEquals(ObjectMappingDAO::SYNC_TO_MAUTIC, $mergedFieldMappings['Lead']['field4']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testCurrentFieldMappingsAreMergedWithJustSyncDirectionUpdated(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject();
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field4' => [
'syncDirection' => ObjectMappingDAO::SYNC_TO_INTEGRATION,
],
];
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field4 = $integrationFields['field4'];
$field4->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(false);
$field4->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(true);
$field4->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertEquals($fields['Lead']['field4']['mappedField'], $mergedFieldMappings['Lead']['field4']['mappedField']);
$this->assertEquals($updatedFieldMappings['field4']['syncDirection'], $mergedFieldMappings['Lead']['field4']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testFieldUnsetIfDirectionIsUpdatedWithoutMappedField(): void
{
$fields = $this->getCurrentFieldMappings();
unset($fields['Lead']['field1']);
$integrationObject = $this->getIntegrationObject();
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => '',
'syncDirection' => 'bidirectional',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertFalse(isset($mergedFieldMappings['Lead']['field1']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testDefaultSyncDirectionSetWithExisting(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject();
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field4 = $integrationFields['field4'];
$field4->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(true);
$field4->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(true);
$field4->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field4' => [
'mappedField' => 'mautic_test_field',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertEquals(ObjectMappingDAO::SYNC_TO_MAUTIC, $mergedFieldMappings['Lead']['field4']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testDefaultSyncDirectionSetWithBidirectionalSupported(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject();
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field1 = $integrationFields['field1'];
$field1->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(true);
$field1->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(true);
$field1->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertEquals(ObjectMappingDAO::SYNC_BIDIRECTIONALLY, $mergedFieldMappings['Lead']['field1']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testDefaultSyncDirectionSetWithIntegrationDirectionalSupported(): void
{
$fields = $this->getCurrentFieldMappings();
unset($fields['Lead']['field1']);
$integrationObject = $this->getIntegrationObject();
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field1 = $integrationFields['field1'];
$field1->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(true);
$field1->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertEquals(ObjectMappingDAO::SYNC_TO_INTEGRATION, $mergedFieldMappings['Lead']['field1']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testDefaultSyncDirectionSetWithMauticDirectionalSupported(): void
{
$fields = $this->getCurrentFieldMappings();
unset($fields['Lead']['field1']);
$integrationObject = $this->getIntegrationObject();
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field1 = $integrationFields['field1'];
$field1->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertEquals(ObjectMappingDAO::SYNC_TO_MAUTIC, $mergedFieldMappings['Lead']['field1']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testCurrentSyncDirectionOverwrittenWithSupportedDirectionalSync(): void
{
$fields = $this->getCurrentFieldMappings();
$integrationObject = $this->getIntegrationObject();
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field1 = $integrationFields['field1'];
$field1->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(true);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
$mergedFieldMappings = $fieldMergerHelper->getFieldMappings();
$this->assertTrue(isset($mergedFieldMappings['Lead']['field1']));
$this->assertEquals(ObjectMappingDAO::SYNC_TO_MAUTIC, $mergedFieldMappings['Lead']['field1']['syncDirection']);
$this->assertTrue(isset($mergedFieldMappings['Lead']['field2']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field3']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field4']));
$this->assertTrue(isset($mergedFieldMappings['Lead']['field5']));
}
public function testDefaultSyncDirectionThrowsExceptionIfFieldDoesNotHaveSyncDirectionSupportDefined(): void
{
$this->expectException(InvalidFormOptionException::class);
$fields = $this->getCurrentFieldMappings();
unset($fields['Lead']['field1']);
$integrationObject = $this->getIntegrationObject();
$integrationFields = $integrationObject->getAllFieldsForMapping('Lead');
/** @var MappedFieldInfoInterface|\PHPUnit\Framework\MockObject\MockObject $field1 */
$field1 = $integrationFields['field1'];
$field1->expects($this->once())
->method('isBidirectionalSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToIntegrationSyncEnabled')
->willReturn(false);
$field1->expects($this->once())
->method('isToMauticSyncEnabled')
->willReturn(false);
$fieldMergerHelper = new FieldMergerHelper($integrationObject, $fields);
$updatedFieldMappings = [
'field1' => [
'mappedField' => 'mautic_test_field',
],
];
$fieldMergerHelper->mergeSyncFieldMapping('Lead', $updatedFieldMappings);
}
/**
* @return \PHPUnit\Framework\MockObject\MockObject|ConfigFormSyncInterface
*/
private function getIntegrationObject(bool $removeFirstField = false): ConfigFormSyncInterface
{
$field1 = $this->createMock(MappedFieldInfoInterface::class);
$field1->method('getName')
->willReturn('field one');
$field2 = $this->createMock(MappedFieldInfoInterface::class);
$field2->method('getName')
->willReturn('field two');
$field3 = $this->createMock(MappedFieldInfoInterface::class);
$field3->method('getName')
->willReturn('field three');
$field4 = $this->createMock(MappedFieldInfoInterface::class);
$field4->method('getName')
->willReturn('field four');
$field5 = $this->createMock(MappedFieldInfoInterface::class);
$field5->method('getName')
->willReturn('field five');
$fields = [
'field1' => $field1,
'field2' => $field2,
'field3' => $field3,
'field4' => $field4,
'field5' => $field5,
];
if ($removeFirstField) {
unset($fields['field1']);
}
$integrationObject = $this->createMock(ConfigFormSyncInterface::class);
$integrationObject->method('getAllFieldsForMapping')
->willReturn($fields);
return $integrationObject;
}
private function getCurrentFieldMappings(): array
{
return [
'Lead' => [
'field1' => [
'mappedField' => 'mautic_field1',
'syncDirection' => ObjectMappingDAO::SYNC_BIDIRECTIONALLY,
],
'field2' => [
'mappedField' => 'mautic_field2',
'syncDirection' => ObjectMappingDAO::SYNC_BIDIRECTIONALLY,
],
'field3' => [
'mappedField' => 'mautic_field3',
'syncDirection' => ObjectMappingDAO::SYNC_BIDIRECTIONALLY,
],
'field4' => [
'mappedField' => 'mautic_field4',
'syncDirection' => ObjectMappingDAO::SYNC_TO_MAUTIC,
],
'field5' => [
'mappedField' => 'mautic_field5',
'syncDirection' => ObjectMappingDAO::SYNC_TO_INTEGRATION,
],
],
];
}
}

View File

@@ -0,0 +1,58 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Integration;
use Mautic\IntegrationsBundle\DTO\Note;
use Mautic\IntegrationsBundle\Integration\ConfigFormNotesTrait;
use Mautic\IntegrationsBundle\Integration\Interfaces\ConfigFormNotesInterface;
use PHPUnit\Framework\TestCase;
class ConfigFormNotesTraitTest extends TestCase
{
public function testConfigFormNotesTraitFormDefaultValues(): void
{
$configFormNotes = new class implements ConfigFormNotesInterface {
use ConfigFormNotesTrait;
};
$this->assertNull($configFormNotes->getAuthorizationNote());
$this->assertNull($configFormNotes->getFeaturesNote());
$this->assertNull($configFormNotes->getFieldMappingNote());
}
public function testConfigFormNotesTraitFormForCustomValues(): void
{
$configFormNotes = new class implements ConfigFormNotesInterface {
use ConfigFormNotesTrait;
public function getAuthorizationNote(): Note
{
return new Note('Authorisation', Note::TYPE_WARNING);
}
public function getFeaturesNote(): Note
{
return new Note('Features', Note::TYPE_INFO);
}
public function getFieldMappingNote(): Note
{
return new Note('Field Mapping', Note::TYPE_WARNING);
}
};
$this->assertInstanceOf(Note::class, $configFormNotes->getAuthorizationNote());
$this->assertSame(Note::TYPE_WARNING, $configFormNotes->getAuthorizationNote()->getType());
$this->assertSame('Authorisation', $configFormNotes->getAuthorizationNote()->getNote());
$this->assertInstanceOf(Note::class, $configFormNotes->getFeaturesNote());
$this->assertSame(Note::TYPE_INFO, $configFormNotes->getFeaturesNote()->getType());
$this->assertSame('Features', $configFormNotes->getFeaturesNote()->getNote());
$this->assertInstanceOf(Note::class, $configFormNotes->getFieldMappingNote());
$this->assertSame(Note::TYPE_WARNING, $configFormNotes->getFieldMappingNote()->getType());
$this->assertSame('Field Mapping', $configFormNotes->getFieldMappingNote()->getNote());
}
}

View File

@@ -0,0 +1,100 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO;
use Mautic\IntegrationsBundle\Exception\InvalidValueException;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\ObjectIdsDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use PHPUnit\Framework\TestCase;
class InputOptionsDAOTest extends TestCase
{
public function testWorkflowFromCliWithAllValuesSet(): void
{
$inputOptionsDAO = new InputOptionsDAO(
[
'integration' => 'Magento',
'first-time-sync' => true,
'disable-push' => false,
'disable-pull' => true,
'disable-activity-push' => true,
'mautic-object-id' => ['contact:12', 'contact:13', 'company:45'],
'integration-object-id' => ['Lead:hfskjdhf', 'Lead:hfskjdhr'],
'start-datetime' => '2019-09-12T12:01:20',
'end-datetime' => '2019-10-12T12:01:20',
'option' => ['custom1:1', 'custom2:2'],
]
);
$this->assertSame('Magento', $inputOptionsDAO->getIntegration());
$this->assertTrue($inputOptionsDAO->isFirstTimeSync());
$this->assertFalse($inputOptionsDAO->pullIsEnabled());
$this->assertTrue($inputOptionsDAO->pushIsEnabled());
$this->assertFalse($inputOptionsDAO->activityPushIsEnabled());
$this->assertSame(['12', '13'], $inputOptionsDAO->getMauticObjectIds()->getObjectIdsFor(Contact::NAME));
$this->assertSame(['45'], $inputOptionsDAO->getMauticObjectIds()->getObjectIdsFor(MauticSyncDataExchange::OBJECT_COMPANY));
$this->assertSame(['hfskjdhf', 'hfskjdhr'], $inputOptionsDAO->getIntegrationObjectIds()->getObjectIdsFor('Lead'));
$this->assertSame('2019-09-12T12:01:20+00:00', $inputOptionsDAO->getStartDateTime()->format(DATE_ATOM));
$this->assertSame('2019-10-12T12:01:20+00:00', $inputOptionsDAO->getEndDateTime()->format(DATE_ATOM));
$this->assertSame(['custom1' => '1', 'custom2' => '2'], $inputOptionsDAO->getOptions());
}
public function testWorkflowFromCliWithNoValuesSet(): void
{
$this->expectException(InvalidValueException::class);
new InputOptionsDAO([]);
}
public function testWorkflowFromCliWithOnlyIntegrationValuesSet(): void
{
$inputOptionsDAO = new InputOptionsDAO(['integration' => 'Magento']);
$this->assertSame('Magento', $inputOptionsDAO->getIntegration());
$this->assertFalse($inputOptionsDAO->isFirstTimeSync());
$this->assertTrue($inputOptionsDAO->pullIsEnabled());
$this->assertTrue($inputOptionsDAO->pushIsEnabled());
$this->assertTrue($inputOptionsDAO->activityPushIsEnabled());
$this->assertNull($inputOptionsDAO->getMauticObjectIds());
$this->assertNull($inputOptionsDAO->getIntegrationObjectIds());
$this->assertNull($inputOptionsDAO->getStartDateTime());
$this->assertNull($inputOptionsDAO->getEndDateTime());
$this->assertEmpty($inputOptionsDAO->getOptions());
}
public function testWorkflowFromServiceWithAllValuesSet(): void
{
$mauticObjectIds = new ObjectIdsDAO();
$integrationObjectIds = new ObjectIdsDAO();
$start = new \DateTimeImmutable('2019-09-12T12:01:20', new \DateTimeZone('UTC'));
$end = new \DateTimeImmutable('2019-10-12T12:01:20', new \DateTimeZone('UTC'));
$options = ['custom1' => 1, 'custom2' => 2];
$inputOptionsDAO = new InputOptionsDAO(
[
'integration' => 'Magento',
'first-time-sync' => true,
'disable-push' => false,
'disable-pull' => true,
'disable-activity-push' => false,
'mautic-object-id' => $mauticObjectIds,
'integration-object-id' => $integrationObjectIds,
'start-datetime' => $start,
'end-datetime' => $end,
'options' => $options,
]
);
$this->assertSame('Magento', $inputOptionsDAO->getIntegration());
$this->assertTrue($inputOptionsDAO->isFirstTimeSync());
$this->assertFalse($inputOptionsDAO->pullIsEnabled());
$this->assertTrue($inputOptionsDAO->pushIsEnabled());
$this->assertTrue($inputOptionsDAO->activityPushIsEnabled());
$this->assertSame($mauticObjectIds, $inputOptionsDAO->getMauticObjectIds());
$this->assertSame($integrationObjectIds, $inputOptionsDAO->getIntegrationObjectIds());
$this->assertSame($start, $inputOptionsDAO->getStartDateTime());
$this->assertSame($end, $inputOptionsDAO->getEndDateTime());
$this->assertSame($options, $inputOptionsDAO->getOptions());
}
}

View File

@@ -0,0 +1,111 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO\Mapping;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use PHPUnit\Framework\TestCase;
class MappingManualDAOTest extends TestCase
{
private $integrationName = 'Test';
private $integrationObjectName = 'Contact';
public function testMappedIntegrationNamesAreReturnedBasedOnInternalObjectName(): void
{
$this->assertEquals(
[$this->integrationObjectName],
$this->getMappingManualDAO()->getIntegrationObjectNames(Contact::NAME)
);
}
public function testMappedInternalNamesAreReturnedBasedOnIntegrationObjectName(): void
{
$this->assertEquals(
[Contact::NAME],
$this->getMappingManualDAO()->getInternalObjectNames($this->integrationObjectName)
);
}
public function testThatOneWayInternalObjectFieldsAreNotReturnedWhenNotRequired(): void
{
$this->assertEquals(
[
'email', // required and bidirectional
'country', // bidirectional
'firstname', // sync from mautic to integration
],
$this->getMappingManualDAO()->getInternalObjectFieldsToSyncToIntegration(Contact::NAME)
);
}
public function testThatRequiredInternalObjectFieldsAreReturned(): void
{
$this->assertEquals(
['email'],
$this->getMappingManualDAO()->getInternalObjectRequiredFieldNames(Contact::NAME)
);
}
public function testThatOneWayIntegrationObjectFieldsAreNotReturnedWhenNotRequired(): void
{
$this->assertEquals(
[
'email', // required and bidirectional
'country', // bidirectional
'last_name', // sync from mautic to integration
],
$this->getMappingManualDAO()->getIntegrationObjectFieldsToSyncToMautic($this->integrationObjectName)
);
}
public function testThatRequiredIntegrationObjectFieldsAreReturned(): void
{
$this->assertEquals(
['email'],
$this->getMappingManualDAO()->getIntegrationObjectRequiredFieldNames($this->integrationObjectName)
);
}
public function testMappedIntegrationFieldIsReturned(): void
{
$this->assertEquals(
'last_name',
$this->getMappingManualDAO()->getIntegrationMappedField(
$this->integrationObjectName,
Contact::NAME,
'lastname'
)
);
}
public function testMappedInternalFieldIsReturned(): void
{
$this->assertEquals(
'lastname',
$this->getMappingManualDAO()->getInternalMappedField(
Contact::NAME,
$this->integrationObjectName,
'last_name'
)
);
}
private function getMappingManualDAO()
{
$mappingManual = new MappingManualDAO($this->integrationName);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $this->integrationObjectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('country', 'country', ObjectMappingDAO::SYNC_BIDIRECTIONALLY);
$objectMapping->addFieldMapping('firstname', 'first_name', ObjectMappingDAO::SYNC_TO_INTEGRATION);
$objectMapping->addFieldMapping('lastname', 'last_name', ObjectMappingDAO::SYNC_TO_MAUTIC);
$mappingManual->addObjectMapping($objectMapping);
return $mappingManual;
}
}

View File

@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\ObjectIdsDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use PHPUnit\Framework\TestCase;
class ObjectIdsDAOTest extends TestCase
{
public function testWorkflow(): void
{
$objectIdsDAO = ObjectIdsDAO::createFromCliOptions(
[
'contact:123',
'contact:124',
'company:12',
'company:13',
'Lead:sowiern',
'Lead:sowie4n',
]
);
$objectIdsDAO->addObjectId('company', '234');
$this->assertSame(['123', '124'], $objectIdsDAO->getObjectIdsFor('contact'));
$this->assertSame(['12', '13', '234'], $objectIdsDAO->getObjectIdsFor('company'));
$this->assertSame(['sowiern', 'sowie4n'], $objectIdsDAO->getObjectIdsFor('Lead'));
$this->expectException(ObjectNotFoundException::class);
$objectIdsDAO->getObjectIdsFor('Unicorn');
}
}

View File

@@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\RelationsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\RelationDAO;
use PHPUnit\Framework\TestCase;
class RelationsDAOTest extends TestCase
{
public function testAddRelations(): void
{
$relationsDAO = new RelationsDAO();
$integrationObjectId = 'IntegrationId-123';
$integrationRelObjectId = 'IntegrationId-456';
$objectName = 'Contact';
$relObjectName = 'Account';
$relationObject = new RelationDAO(
$objectName,
$relObjectName,
$relObjectName,
$integrationObjectId,
$integrationRelObjectId
);
$relations = ['AccountId' => $relationObject];
$relationsDAO->addRelations($relations);
$this->assertEquals($relationsDAO->current(), $relationObject);
$this->assertEquals($relationsDAO->current()->getObjectName(), $objectName);
$this->assertEquals($relationsDAO->current()->getRelObjectName(), $relObjectName);
$this->assertEquals($relationsDAO->current()->getObjectIntegrationId(), $integrationObjectId);
$this->assertEquals($relationsDAO->current()->getRelObjectIntegrationId(), $integrationRelObjectId);
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO\Sync\Order;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO as ReportFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class ObjectChangeDAOTest extends TestCase
{
public function testGetUnchangedFields(): void
{
$fieldDAO = new FieldDAO('email', new NormalizedValueDAO('email', 'test@test.com'));
$objectChangeDAO = new ObjectChangeDAO('foo', 'bar', 1, 'contact', 1);
$objectChangeDAO->addField($fieldDAO, ReportFieldDAO::FIELD_UNCHANGED);
$unchangedFields = $objectChangeDAO->getUnchangedFields();
Assert::assertCount(1, $unchangedFields);
Assert::assertArrayHasKey('email', $unchangedFields);
Assert::assertSame($fieldDAO, $unchangedFields['email']);
}
public function testSetAndGetObjectMapping(): void
{
$objectChangeDAO = new ObjectChangeDAO('foo', 'bar', 1, 'contact', 1);
$objectMapping = new ObjectMapping();
$objectChangeDAO->setObjectMapping($objectMapping);
Assert::assertSame($objectMapping, $objectChangeDAO->getObjectMapping());
}
public function testThatFieldCanBeRemoved(): void
{
$objectChangeDAO = new ObjectChangeDAO('foo', 'bar', 1, 'contact', 1);
$value = new NormalizedValueDAO('type', 1);
$field = new FieldDAO('fieldName', $value);
Assert::assertCount(0, $objectChangeDAO->getFields());
$objectChangeDAO->addField($field);
Assert::assertCount(1, $objectChangeDAO->getFields());
$objectChangeDAO->removeField('fieldName');
Assert::assertCount(0, $objectChangeDAO->getFields());
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO\Sync\Order;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectMappingsDAO;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class ObjectMappingsDAOTest extends TestCase
{
public function testGetters(): void
{
$objectMappings = new ObjectMappingsDAO();
$objectMappings->addNewObjectMapping((new ObjectMapping())->setIntegrationObjectName('foonew'));
$objectMappings->addNewObjectMapping((new ObjectMapping())->setIntegrationObjectName('barnew'));
$mappings = $objectMappings->getNewMappings();
Assert::assertCount(2, $mappings);
Assert::assertEquals('foonew', $mappings[0]->getIntegrationObjectName());
Assert::assertEquals('barnew', $mappings[1]->getIntegrationObjectName());
$objectMappings->addUpdatedObjectMapping((new ObjectMapping())->setIntegrationObjectName('fooupdate'));
$objectMappings->addUpdatedObjectMapping((new ObjectMapping())->setIntegrationObjectName('barupdate'));
$mappings = $objectMappings->getUpdatedMappings();
Assert::assertCount(2, $mappings);
Assert::assertEquals('fooupdate', $mappings[0]->getIntegrationObjectName());
Assert::assertEquals('barupdate', $mappings[1]->getIntegrationObjectName());
}
}

View File

@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO\Sync\Order;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\OrderDAO;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
final class OrderDAOTest extends TestCase
{
/**
* Test that the retry object is removed from the synced objects and the success object is present.
*/
public function testGetSuccessfullySyncedObjects(): void
{
$orderDAO = new OrderDAO(new \DateTimeImmutable(), false, 'IntegrationA');
$successObject = new ObjectChangeDAO('IntegrationA', 'Contact', 'integration-id-1', 'lead', 123);
$retryObject = new ObjectChangeDAO('IntegrationA', 'Contact', 'integration-id-2', 'lead', 456);
$orderDAO->addObjectChange($successObject);
$orderDAO->addObjectChange($retryObject);
$orderDAO->retrySyncLater($retryObject);
Assert::assertSame(
[$successObject],
$orderDAO->getSuccessfullySyncedObjects()
);
}
}

View File

@@ -0,0 +1,124 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\DAO\Sync\Order;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\RemappedObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\OrderResultsDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class OrderResultsDAOTest extends TestCase
{
public function testObjectsOrganizedByObjectName(): void
{
$newObjectMapping1 = new ObjectMapping();
$newObjectMapping1->setIntegrationObjectName('foo');
$newObjectMapping1->setIntegrationObjectId('abc');
$newObjectMapping2 = new ObjectMapping();
$newObjectMapping2->setIntegrationObjectName('bar');
$newObjectMapping2->setIntegrationObjectId('efg');
$newObjectMappings = [$newObjectMapping1, $newObjectMapping2];
$updatedObjectMapping1 = new ObjectMapping();
$updatedObjectMapping1->setIntegrationObjectName('foo');
$updatedObjectMapping1->setIntegrationObjectId('hij');
$updatedObjectMapping2 = new ObjectMapping();
$updatedObjectMapping2->setIntegrationObjectName('bar');
$updatedObjectMapping1->setIntegrationObjectId('klm');
$updatedObjectMappings = [$updatedObjectMapping1, $updatedObjectMapping2];
$remappedObjects = [
new RemappedObjectDAO('foobar', 'oldfoo', 'oldfoo1', 'foo', 'foo1'),
new RemappedObjectDAO('foobar', 'oldbar', 'oldbar1', 'bar', 'bar1'),
];
$deletedObjects = [
new ObjectChangeDAO('foobar', 'foo', 'foo1', 'contact', 1),
new ObjectChangeDAO('foobar', 'bar', 'bar1', 'company', 1),
];
$orderResults = new OrderResultsDAO($newObjectMappings, $updatedObjectMappings, $remappedObjects, $deletedObjects);
$fooNewObjectMappings = $orderResults->getNewObjectMappings('foo');
Assert::assertCount(1, $fooNewObjectMappings);
Assert::assertEquals('abc', $fooNewObjectMappings[0]->getIntegrationObjectId());
$barNewObjectMappings = $orderResults->getNewObjectMappings('bar');
Assert::assertCount(1, $barNewObjectMappings);
Assert::assertEquals('efg', $barNewObjectMappings[0]->getIntegrationObjectId());
$fooRemappedObjects = $orderResults->getRemappedObjects('foo');
Assert::assertCount(1, $fooRemappedObjects);
Assert::assertEquals('foo1', $fooRemappedObjects[0]->getNewObjectId());
$barRemappedObjects = $orderResults->getRemappedObjects('bar');
Assert::assertCount(1, $barRemappedObjects);
Assert::assertEquals('bar1', $barRemappedObjects[0]->getNewObjectId());
$fooDeletedObjects = $orderResults->getDeletedObjects('foo');
Assert::assertCount(1, $fooDeletedObjects);
Assert::assertEquals('foo1', $fooDeletedObjects[0]->getObjectId());
$barDeletedObjects = $orderResults->getDeletedObjects('bar');
Assert::assertCount(1, $barDeletedObjects);
Assert::assertEquals('bar1', $barDeletedObjects[0]->getObjectId());
}
public function testExceptionThrownIfObjectNotFoundForNewObjectMappings(): void
{
$this->expectException(ObjectNotFoundException::class);
$orderResults = new OrderResultsDAO([], [], [], []);
$orderResults->getNewObjectMappings('foo');
}
public function testExceptionThrownIfObjectNotFoundForUpdatedObjectMappings(): void
{
$this->expectException(ObjectNotFoundException::class);
$orderResults = new OrderResultsDAO([], [], [], []);
$orderResults->getUpdatedObjectMappings('foo');
}
public function testExceptionThrownIfObjectNotFoundForRemappedObjects(): void
{
$this->expectException(ObjectNotFoundException::class);
$orderResults = new OrderResultsDAO([], [], [], []);
$orderResults->getRemappedObjects('foo');
}
public function testExceptionThrownIfObjectNotFoundForDeletedObjects(): void
{
$this->expectException(ObjectNotFoundException::class);
$orderResults = new OrderResultsDAO([], [], [], []);
$orderResults->getDeletedObjects('foo');
}
public function testGetObjectMappingsReturnsMergedNewAndUpdated(): void
{
$newObjectMapping = new ObjectMapping();
$newObjectMapping->setIntegrationObjectName('foo');
$newObjectMapping->setIntegrationObjectId('abc');
$updatedObjectMapping = new ObjectMapping();
$updatedObjectMapping->setIntegrationObjectName('foo');
$updatedObjectMapping->setIntegrationObjectId('hij');
$orderResults = new OrderResultsDAO([$newObjectMapping], [$updatedObjectMapping], [], []);
$objectMappings = $orderResults->getObjectMappings('foo');
Assert::assertCount(2, $objectMappings);
Assert::assertEquals('abc', $objectMappings[0]->getIntegrationObjectId());
Assert::assertEquals('hij', $objectMappings[1]->getIntegrationObjectId());
$objectMappings = $orderResults->getObjectMappings('bar');
Assert::assertEmpty($objectMappings);
}
}

View File

@@ -0,0 +1,373 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Helper;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Entity\ObjectMappingRepository;
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\UpdatedObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectDeletedException;
use Mautic\IntegrationsBundle\Sync\Helper\MappingHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class MappingHelperTest extends TestCase
{
/**
* @var MockObject&FieldsWithUniqueIdentifier
*/
private MockObject $fieldsWithUniqueIdentifier;
/**
* @var ObjectProvider&MockObject
*/
private MockObject $objectProvider;
/**
* @var EventDispatcherInterface&MockObject
*/
private MockObject $dispatcher;
/**
* @var ObjectMappingRepository&MockObject
*/
private MockObject $objectMappingRepository;
private MappingHelper $mappingHelper;
protected function setUp(): void
{
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->objectMappingRepository = $this->createMock(ObjectMappingRepository::class);
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
$this->mappingHelper = new MappingHelper(
$this->fieldsWithUniqueIdentifier,
$this->objectMappingRepository,
$this->objectProvider,
$this->dispatcher
);
}
public function testObjectReturnedIfKnownMappingExists(): void
{
$mappingManual = new MappingManualDAO('test');
$integrationObjectDAO = new ObjectDAO('Object', 1);
$internalObjectDAO = [
'internal_object_id' => 1,
'last_sync_date' => '2018-10-01 00:00:00',
'is_deleted' => 0,
];
$this->objectMappingRepository->expects($this->once())
->method('getInternalObject')
->willReturn($internalObjectDAO);
$internalObjectName = 'Contact';
$foundInternalObject = $this->mappingHelper->findMauticObject($mappingManual, $internalObjectName, $integrationObjectDAO);
Assert::assertEquals($internalObjectName, $foundInternalObject->getObject());
Assert::assertEquals($internalObjectDAO['internal_object_id'], $foundInternalObject->getObjectId());
Assert::assertEquals($internalObjectDAO['last_sync_date'], $foundInternalObject->getChangeDateTime()->format('Y-m-d H:i:s'));
}
public function testMauticObjectSearchedAndEmptyObjectReturnedIfNoIdentifierFieldsAreMapped(): void
{
$this->fieldsWithUniqueIdentifier->expects($this->once())
->method('getFieldsWithUniqueIdentifier')
->willReturn([]);
$mappingManual = $this->createMock(MappingManualDAO::class);
$internalObjectName = 'Contact';
$integrationObjectDAO = new ObjectDAO('Object', 1);
$foundInternalObject = $this->mappingHelper->findMauticObject($mappingManual, $internalObjectName, $integrationObjectDAO);
Assert::assertEquals($internalObjectName, $foundInternalObject->getObject());
Assert::assertEquals(null, $foundInternalObject->getObjectId());
}
public function testEmptyObjectIsReturnedWhenMauticContactIsNotFound(): void
{
$this->fieldsWithUniqueIdentifier->expects($this->once())
->method('getFieldsWithUniqueIdentifier')
->willReturn(
[
'email' => 'Email',
]
);
$internalObject = new Contact();
$internalObjectName = Contact::NAME;
$integrationObjectDAO = new ObjectDAO('Object', 1);
$integrationObjectDAO->addField(new FieldDAO('integration_email', new NormalizedValueDAO('email', 'test@test.com')));
$mappingManual = $this->createMock(MappingManualDAO::class);
$mappingManual->expects($this->once())
->method('getIntegrationMappedField')
->with($integrationObjectDAO->getObject(), $internalObjectName, 'email')
->willReturn('integration_email');
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with($internalObjectName)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(
function (InternalObjectFindEvent $event) use ($internalObject) {
Assert::assertSame($internalObject, $event->getObject());
Assert::assertSame(['email' => 'test@test.com'], $event->getFieldValues());
return true;
}
),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$foundInternalObject = $this->mappingHelper->findMauticObject($mappingManual, $internalObjectName, $integrationObjectDAO);
Assert::assertEquals($internalObjectName, $foundInternalObject->getObject());
Assert::assertEquals(null, $foundInternalObject->getObjectId());
}
public function testMauticContactIsFoundAndReturnedAsObjectDAO(): void
{
$this->fieldsWithUniqueIdentifier->expects($this->once())
->method('getFieldsWithUniqueIdentifier')
->willReturn(
[
'email' => 'Email',
]
);
$internalObject = new Contact();
$internalObjectName = Contact::NAME;
$changeDateTime = new \DateTime();
$integrationObjectDAO = new ObjectDAO('Object', 1, $changeDateTime);
$integrationObjectDAO->addField(new FieldDAO('integration_email', new NormalizedValueDAO('email', 'test@test.com')));
$mappingManual = $this->createMock(MappingManualDAO::class);
$mappingManual->expects($this->once())
->method('getIntegrationMappedField')
->with($integrationObjectDAO->getObject(), $internalObjectName, 'email')
->willReturn('integration_email');
$mappingManual->expects($this->exactly(2))
->method('getIntegration')
->willReturn('Test');
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with($internalObjectName)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(
function (InternalObjectFindEvent $event) use ($internalObject) {
Assert::assertSame($internalObject, $event->getObject());
Assert::assertSame(['email' => 'test@test.com'], $event->getFieldValues());
// Mock a subscriber.
$event->setFoundObjects(
[
[
'id' => 3,
],
]
);
return true;
}
),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$foundInternalObject = $this->mappingHelper->findMauticObject($mappingManual, $internalObjectName, $integrationObjectDAO);
Assert::assertEquals($internalObjectName, $foundInternalObject->getObject());
Assert::assertEquals(3, $foundInternalObject->getObjectId());
}
public function testMauticCompanyIsFoundAndReturnedAsObjectDAO(): void
{
$this->fieldsWithUniqueIdentifier->expects($this->once())
->method('getFieldsWithUniqueIdentifier')
->willReturn(
[
'email' => 'Email',
]
);
$internalObject = new Company();
$internalObjectName = Company::NAME;
$changeDateTime = new \DateTime();
$integrationObjectDAO = new ObjectDAO('Object', 1, $changeDateTime);
$integrationObjectDAO->addField(new FieldDAO('integration_email', new NormalizedValueDAO('email', 'test@test.com')));
$mappingManual = $this->createMock(MappingManualDAO::class);
$mappingManual->expects($this->once())
->method('getIntegrationMappedField')
->with($integrationObjectDAO->getObject(), $internalObjectName, 'email')
->willReturn('integration_email');
$mappingManual->expects($this->exactly(2))
->method('getIntegration')
->willReturn('Test');
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with($internalObjectName)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(
function (InternalObjectFindEvent $event) use ($internalObject) {
Assert::assertSame($internalObject, $event->getObject());
Assert::assertSame(['email' => 'test@test.com'], $event->getFieldValues());
// Mock a subscriber.
$event->setFoundObjects(
[
[
'id' => 3,
],
]
);
return true;
}
),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$foundInternalObject = $this->mappingHelper->findMauticObject(
$mappingManual,
$internalObjectName,
$integrationObjectDAO
);
Assert::assertEquals($internalObjectName, $foundInternalObject->getObject());
Assert::assertEquals(3, $foundInternalObject->getObjectId());
}
public function testIntegrationObjectReturnedIfMapped(): void
{
$objectName = 'Object';
$objectId = 1;
$changeDateTime = '2018-10-08 00:00:00';
$this->objectMappingRepository->expects($this->once())
->method('getIntegrationObject')
->willReturn(
[
'is_deleted' => false,
'integration_object_id' => $objectId,
'last_sync_date' => $changeDateTime,
]
);
$foundIntegrationObject = $this->mappingHelper->findIntegrationObject('Test', $objectName, new ObjectDAO('Contact', 1));
Assert::assertEquals($objectName, $foundIntegrationObject->getObject());
Assert::assertEquals($objectId, $foundIntegrationObject->getObjectId());
Assert::assertEquals($changeDateTime, $foundIntegrationObject->getChangeDateTime()->format('Y-m-d H:i:s'));
}
public function testEmptyIntegrationObjectReturnedIfNotMapped(): void
{
$objectName = 'Object';
$this->objectMappingRepository->expects($this->once())
->method('getIntegrationObject')
->willReturn([]);
$foundIntegrationObject = $this->mappingHelper->findIntegrationObject('Test', $objectName, new ObjectDAO('Contact', 1));
Assert::assertEquals($objectName, $foundIntegrationObject->getObject());
Assert::assertEquals(null, $foundIntegrationObject->getObjectId());
Assert::assertEquals(null, $foundIntegrationObject->getChangeDateTime());
}
public function testDeletedExceptionThrownIfIntegrationObjectHasBeenNotedAsDeleted(): void
{
$this->expectException(ObjectDeletedException::class);
$objectName = 'Object';
$objectId = 1;
$changeDateTime = '2018-10-08 00:00:00';
$this->objectMappingRepository->expects($this->once())
->method('getIntegrationObject')
->willReturn(
[
'is_deleted' => true,
'integration_object_id' => $objectId,
'last_sync_date' => $changeDateTime,
]
);
$this->mappingHelper->findIntegrationObject('Test', $objectName, new ObjectDAO('Contact', 1));
}
public function testObjectMappingIsInjectedIntoUpdatedObjectMappingDAO(): void
{
$objectMapping = new ObjectMapping();
$objectMapping->setIntegration('foobar');
$objectMapping->setIntegrationObjectName('foo');
$objectMapping->setIntegrationObjectId('1');
$this->objectMappingRepository->expects($this->once())
->method('findOneBy')
->with(
[
'integration' => $objectMapping->getIntegration(),
'integrationObjectName' => $objectMapping->getIntegrationObjectName(),
'integrationObjectId' => $objectMapping->getIntegrationObjectId(),
]
)
->willReturn($objectMapping);
$updatedObjectMappingDAO = new UpdatedObjectMappingDAO('foobar', 'foo', 1, new \DateTime());
$this->mappingHelper->updateObjectMappings([$updatedObjectMappingDAO]);
Assert::assertSame($objectMapping, $updatedObjectMappingDAO->getObjectMapping());
}
public function testObjectMappingIsNotSetIfObjectMappingNotFoundWhenAttemptingToUpdate(): void
{
$this->objectMappingRepository->expects($this->once())
->method('findOneBy')
->with(
[
'integration' => 'foobar',
'integrationObjectName' => 'foo',
'integrationObjectId' => 1,
]
);
$updatedObjectMappingDAO = new UpdatedObjectMappingDAO('foobar', 'foo', 1, new \DateTime());
$this->mappingHelper->updateObjectMappings([$updatedObjectMappingDAO]);
Assert::assertEmpty($updatedObjectMappingDAO->getObjectMapping());
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Helper;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\RelationsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\RelationDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ReportDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\ReferenceValueDAO;
use Mautic\IntegrationsBundle\Sync\Helper\MappingHelper;
use Mautic\IntegrationsBundle\Sync\Helper\RelationsHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use PHPUnit\Framework\TestCase;
class RelationsHelperTest extends TestCase
{
/**
* @var MappingHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $mappingHelper;
private RelationsHelper $relationsHelper;
/**
* @var ReportDAO|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $syncReport;
/**
* @var MappingManualDAO|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $mappingManual;
protected function setUp(): void
{
$this->mappingHelper = $this->createMock(MappingHelper::class);
$this->relationsHelper = new RelationsHelper($this->mappingHelper);
$this->syncReport = $this->createMock(ReportDAO::class);
$this->mappingManual = $this->createMock(MappingManualDAO::class);
}
public function testProcessRelationsWithUnsychronisedObjects(): void
{
$integrationObjectId = 'IntegrationId-123';
$integrationRelObjectId = 'IntegrationId-456';
$relObjectName = 'Account';
$relationObject = new RelationDAO(
'Contact',
'AccountId',
$relObjectName,
$integrationObjectId,
$integrationRelObjectId
);
$relationsObject = new RelationsDAO();
$relationsObject->addRelation($relationObject);
$this->syncReport->expects($this->once())
->method('getRelations')
->willReturn($relationsObject);
$this->mappingManual->expects($this->any())
->method('getMappedInternalObjectsNames')
->willReturn(['company']);
$internalObject = new ObjectDAO('company', null);
$this->mappingHelper->expects($this->once())
->method('findMauticObject')
->willReturn($internalObject);
$this->relationsHelper->processRelations($this->mappingManual, $this->syncReport);
$objectsToSynchronize = $this->relationsHelper->getObjectsToSynchronize();
$this->assertCount(1, $objectsToSynchronize);
$this->assertEquals($objectsToSynchronize[0]->getObjectId(), $integrationRelObjectId);
$this->assertEquals($objectsToSynchronize[0]->getObject(), $relObjectName);
}
public function testProcessRelationsWithSychronisedObjects(): void
{
$integrationObjectId = 'IntegrationId-123';
$integrationRelObjectId = 'IntegrationId-456';
$internalRelObjectId = 13;
$relObjectName = 'Account';
$relFieldName = 'AccountId';
$referenceVlaue = new ReferenceValueDAO();
$normalizedValue = new NormalizedValueDAO(NormalizedValueDAO::REFERENCE_TYPE, $integrationRelObjectId, $referenceVlaue);
$fieldDao = new FieldDAO('AccountId', $normalizedValue);
$objectDao = new ObjectDAO('Contact', 1);
$objectDao->addField($fieldDao);
$relationObject = new RelationDAO(
'Contact',
$relFieldName,
$relObjectName,
$integrationObjectId,
$integrationRelObjectId
);
$relationsObject = new RelationsDAO();
$relationsObject->addRelation($relationObject);
$this->syncReport->expects($this->once())
->method('getRelations')
->willReturn($relationsObject);
$this->syncReport->expects($this->once())
->method('getObject')
->willReturn($objectDao);
$this->mappingManual->expects($this->any())
->method('getMappedInternalObjectsNames')
->willReturn(['company']);
$internalObject = new ObjectDAO(MauticSyncDataExchange::OBJECT_COMPANY, $internalRelObjectId);
$this->mappingHelper->expects($this->once())
->method('findMauticObject')
->willReturn($internalObject);
$this->relationsHelper->processRelations($this->mappingManual, $this->syncReport);
$objectsToSynchronize = $this->relationsHelper->getObjectsToSynchronize();
$this->assertCount(0, $objectsToSynchronize);
$this->assertEquals($internalRelObjectId, $objectDao->getField($relFieldName)->getValue()->getNormalizedValue()->getValue());
$this->assertEquals(MauticSyncDataExchange::OBJECT_COMPANY, $objectDao->getField($relFieldName)->getValue()->getNormalizedValue()->getType());
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Helper;
use Mautic\IntegrationsBundle\Sync\Helper\SyncDateHelper;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class SyncDateHelperTest extends TestCase
{
/**
* @var SyncDateHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $syncDateHelper;
protected function setUp(): void
{
$this->syncDateHelper = $this->getMockBuilder(SyncDateHelper::class)
->disableOriginalConstructor()
->onlyMethods(['getLastSyncDateForObject'])
->getMock();
}
public function testSpecifiedFromDateTimeIsReturned(): void
{
$syncFromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$this->syncDateHelper->setSyncDateTimes($syncFromDateTime);
Assert::assertEquals($syncFromDateTime, $this->syncDateHelper->getSyncFromDateTime('Test', 'Object'));
}
public function testLastSyncDateForIntegrationSyncObjectIsReturned(): void
{
$objectLastSyncDate = new \DateTimeImmutable('2018-10-08 00:00:00');
$this->syncDateHelper->method('getLastSyncDateForObject')
->willReturn($objectLastSyncDate);
Assert::assertEquals($objectLastSyncDate, $this->syncDateHelper->getSyncFromDateTime('Test', 'Object'));
}
public function testSyncToDateTimeIsReturnedIfSpecified(): void
{
$syncToDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$this->syncDateHelper->setSyncDateTimes(null, $syncToDateTime);
Assert::assertEquals($syncToDateTime, $this->syncDateHelper->getSyncToDateTime());
}
public function testSyncDateTimeIsReturnedForSyncToDateTimeIfNotSpecified(): void
{
$this->syncDateHelper->setSyncDateTimes();
Assert::assertInstanceOf(\DateTimeImmutable::class, $this->syncDateHelper->getSyncToDateTime());
}
public function testThatSetInternalSyncStartDateTimeMethodUsesSyncToDateValueIfItIsEarlier(): void
{
// Although $fiveSecondsBefore value is expected to be in UTC timezone let's use another timezone
// to check how the method handles such cases.
$fiveSecondsBefore = new \DateTime('-5 seconds', new \DateTimeZone('Etc/GMT-5'));
$this->syncDateHelper->setSyncDateTimes(null, $fiveSecondsBefore);
$this->syncDateHelper->setInternalSyncStartDateTime();
$internalSyncStartDateTime = $this->syncDateHelper->getInternalSyncStartDateTime();
Assert::assertSame($fiveSecondsBefore->getTimestamp(), $internalSyncStartDateTime->getTimestamp());
}
public function testThatSetInternalSyncStartDateTimeMethodUsesNowIfItIsEarlier(): void
{
// Although $fiveSecondsAfter value is expected to be in UTC timezone let's use another timezone
// to check how the method handles such cases.
$fiveSecondsAfter = new \DateTime('+5 seconds', new \DateTimeZone('Etc/GMT+5'));
$this->syncDateHelper->setSyncDateTimes(null, $fiveSecondsAfter);
$this->syncDateHelper->setInternalSyncStartDateTime();
$now = new \DateTime('now', new \DateTimeZone('UTC'));
$internalSyncStartDateTime = $this->syncDateHelper->getInternalSyncStartDateTime();
$difference = $internalSyncStartDateTime->getTimestamp() - $now->getTimestamp();
// Add a 1 second buffer in case there is some delay
Assert::assertTrue((1 >= $difference) && (-1 < $difference));
}
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Notification\Handler;
use Mautic\IntegrationsBundle\Sync\Exception\HandlerNotSupportedException;
use Mautic\IntegrationsBundle\Sync\Notification\Handler\HandlerContainer;
use Mautic\IntegrationsBundle\Sync\Notification\Handler\HandlerInterface;
use PHPUnit\Framework\TestCase;
class HandlerContainerTest extends TestCase
{
public function testExceptionThrownIfIntegrationNotFound(): void
{
$this->expectException(HandlerNotSupportedException::class);
$handler = new HandlerContainer();
$handler->getHandler('foo', 'bar');
}
public function testExceptionThrownIfObjectNotFound(): void
{
$this->expectException(HandlerNotSupportedException::class);
$handler = new HandlerContainer();
$mockHandler = $this->createMock(HandlerInterface::class);
$mockHandler->method('getIntegration')
->willReturn('foo');
$mockHandler->method('getSupportedObject')
->willReturn('bogus');
$handler->registerHandler($mockHandler);
$handler->getHandler('foo', 'bar');
}
public function testHandlerIsRegistered(): void
{
$handler = new HandlerContainer();
$mockHandler = $this->createMock(HandlerInterface::class);
$mockHandler->method('getIntegration')
->willReturn('foo');
$mockHandler->method('getSupportedObject')
->willReturn('bar');
$handler->registerHandler($mockHandler);
$returnedHandler = $handler->getHandler('foo', 'bar');
$this->assertEquals($mockHandler, $returnedHandler);
}
}

View File

@@ -0,0 +1,83 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Notification\Helper;
use Mautic\IntegrationsBundle\Event\InternalObjectOwnerEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotSupportedException;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\OwnerProvider;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class OwnerProviderTest extends TestCase
{
/**
* @var ObjectProvider|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $objectProvider;
/**
* @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $dispatcher;
private OwnerProvider $ownerProvider;
protected function setUp(): void
{
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->ownerProvider = new OwnerProvider($this->dispatcher, $this->objectProvider);
}
public function testGetOwnersForObjectIdsWithoutIds(): void
{
$this->objectProvider->expects($this->never())
->method('getObjectByName');
$this->assertSame([], $this->ownerProvider->getOwnersForObjectIds(Contact::NAME, []));
}
public function testGetOwnersForObjectIdsWithUnknownObject(): void
{
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with('Unicorn')
->willThrowException(new ObjectNotFoundException('Unicorn'));
$this->expectException(ObjectNotSupportedException::class);
$this->ownerProvider->getOwnersForObjectIds('Unicorn', [123]);
}
public function testGetOwnersForObjectIdsWithKnownObject(): void
{
$internalObject = new Contact();
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectOwnerEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame([123], $event->getObjectIds());
// Simulate a subscriber. Format: [object_id => owner_id].
$event->setOwners([$event->getObjectIds()[0] => 456]);
return true;
}),
IntegrationEvents::INTEGRATION_FIND_OWNER_IDS
);
$this->assertSame([123 => 456], $this->ownerProvider->getOwnersForObjectIds(Contact::NAME, [123]));
}
}

View File

@@ -0,0 +1,172 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Notification\Helper;
use Mautic\IntegrationsBundle\Event\InternalObjectRouteEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotSupportedException;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\RouteHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class RouteHelperTest extends TestCase
{
/**
* @var ObjectProvider|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $objectProvider;
/**
* @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $dispatcher;
private RouteHelper $routeHelper;
protected function setUp(): void
{
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->routeHelper = new RouteHelper($this->objectProvider, $this->dispatcher);
}
public function testContactRoute(): void
{
$internalObject = new Contact();
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectRouteEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame(1, $event->getId());
// Mock a subscriber.
$event->setRoute('route/for/id/1');
return true;
}),
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE
);
$this->routeHelper->getRoute(Contact::NAME, 1);
}
public function testCompanyRoute(): void
{
$internalObject = new Company();
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Company::NAME)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectRouteEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame(1, $event->getId());
// Mock a subscriber.
$event->setRoute('route/for/id/1');
return true;
}),
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE
);
$this->routeHelper->getRoute(Company::NAME, 1);
}
public function testExceptionThrownWithUnsupportedObject(): void
{
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with('FooBar')
->willThrowException(new ObjectNotFoundException('FooBar object not found'));
$this->dispatcher->expects($this->never())->method('dispatch');
$this->expectException(ObjectNotSupportedException::class);
$this->routeHelper->getRoute('FooBar', 1);
}
public function testLink(): void
{
$internalObject = new Contact();
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectRouteEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame(1, $event->getId());
// Mock a subscriber.
$event->setRoute('route/for/id/1');
return true;
}),
IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE
);
$link = $this->routeHelper->getLink(Contact::NAME, 1, 'Hello');
$this->assertEquals('<a href="route/for/id/1">Hello</a>', $link);
}
public function testLinkCsv(): void
{
$internalObject = new Contact();
$this->objectProvider->expects($this->exactly(2))
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
$matcher = $this->exactly(2);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher, $internalObject) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectRouteEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame(1, $event->getId());
// Mock a subscriber.
$event->setRoute('route/for/id/1');
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectRouteEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame(2, $event->getId());
// Mock a subscriber.
$event->setRoute('route/for/id/2');
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_BUILD_INTERNAL_OBJECT_ROUTE, $parameters[1]);
}
return $parameters[0];
});
$csv = $this->routeHelper->getLinkCsv(Contact::NAME, [1, 2]);
$this->assertEquals('[<a href="route/for/id/1">1</a>], [<a href="route/for/id/2">2</a>]', $csv);
}
}

View File

@@ -0,0 +1,127 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Notification\Helper;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\OwnerProvider;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\RouteHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\UserHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\UserNotificationBuilder;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\UserNotificationHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Writer;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
class UserNotificationHelperTest extends TestCase
{
/**
* @var Writer|MockObject
*/
private MockObject $writer;
/**
* @var UserHelper|MockObject
*/
private MockObject $userHelper;
/**
* @var OwnerProvider|MockObject
*/
private MockObject $ownerProvider;
/**
* @var RouteHelper|MockObject
*/
private MockObject $routeHelper;
/**
* @var TranslatorInterface|MockObject
*/
private MockObject $translator;
private UserNotificationHelper $helper;
protected function setUp(): void
{
$this->writer = $this->createMock(Writer::class);
$this->userHelper = $this->createMock(UserHelper::class);
$this->ownerProvider = $this->createMock(OwnerProvider::class);
$this->routeHelper = $this->createMock(RouteHelper::class);
$this->translator = $this->createMock(TranslatorInterface::class);
$userNotificationBuilder = new UserNotificationBuilder($this->userHelper,
$this->ownerProvider,
$this->routeHelper,
$this->translator
);
$this->helper = new UserNotificationHelper($this->writer, $userNotificationBuilder);
}
public function testNotificationSentToOwner(): void
{
$this->ownerProvider->expects($this->once())
->method('getOwnersForObjectIds')
->with(Contact::NAME, [1])
->willReturn([['owner_id' => 1, 'id' => 1]]);
$this->userHelper->expects($this->never())
->method('getAdminUsers');
$matcher = $this->exactly(2);
$this->translator->expects($matcher)
->method('trans')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.header', $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.sync_error', $parameters[0]);
}
return 'test';
});
$this->writer->expects($this->once())
->method('writeUserNotification');
$this->routeHelper->expects($this->once())
->method('getLink');
$this->helper->writeNotification('test', 'test', 'test', Contact::NAME, 1, 'foobar');
}
public function testNotificationSentToAdmins(): void
{
$this->ownerProvider->expects($this->once())
->method('getOwnersForObjectIds')
->with(Contact::NAME, [1])
->willReturn([]);
$this->userHelper->expects($this->once())
->method('getAdminUsers')
->willReturn([1]);
$matcher = $this->exactly(2);
$this->translator->expects($matcher)
->method('trans')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.header', $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.sync_error', $parameters[0]);
}
return 'test';
});
$this->writer->expects($this->once())
->method('writeUserNotification');
$this->routeHelper->expects($this->once())
->method('getLink');
$this->helper->writeNotification('test', 'test', 'test', Contact::NAME, 1, 'foobar');
}
}

View File

@@ -0,0 +1,213 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\Notification\Helper;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\OwnerProvider;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\RouteHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\UserHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Helper\UserSummaryNotificationHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Writer;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Contracts\Translation\TranslatorInterface;
class UserSummaryNotificationHelperTest extends TestCase
{
/**
* @var Writer|MockObject
*/
private MockObject $writer;
/**
* @var UserHelper|MockObject
*/
private MockObject $userHelper;
/**
* @var OwnerProvider|MockObject
*/
private MockObject $ownerProvider;
/**
* @var RouteHelper|MockObject
*/
private MockObject $routeHelper;
/**
* @var TranslatorInterface|MockObject
*/
private MockObject $translator;
private UserSummaryNotificationHelper $helper;
protected function setUp(): void
{
$this->writer = $this->createMock(Writer::class);
$this->userHelper = $this->createMock(UserHelper::class);
$this->ownerProvider = $this->createMock(OwnerProvider::class);
$this->routeHelper = $this->createMock(RouteHelper::class);
$this->translator = $this->createMock(TranslatorInterface::class);
$this->helper = new UserSummaryNotificationHelper(
$this->writer,
$this->userHelper,
$this->ownerProvider,
$this->routeHelper,
$this->translator
);
}
public function testNotificationSentToOwner(): void
{
$this->helper->storeSummaryNotification('Foo', 'Bar', 1);
$this->helper->storeSummaryNotification('Bar', 'Foo', 2);
$matcher = $this->exactly(2);
$this->ownerProvider->expects($matcher)
->method('getOwnersForObjectIds')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(Contact::NAME, $parameters[0]);
$this->assertSame([1 => 1], $parameters[1]);
return [['owner_id' => 1, 'id' => 1]];
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(Contact::NAME, $parameters[0]);
$this->assertSame([2 => 2], $parameters[1]);
return [['owner_id' => 2, 'id' => 2]];
}
});
$this->userHelper->expects($this->never())
->method('getAdminUsers');
$matcher = $this->exactly(4);
$this->translator->expects($matcher)
->method('trans')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.header', $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('test', $parameters[0]);
}
if (3 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.header', $parameters[0]);
}
if (4 === $matcher->numberOfInvocations()) {
$this->assertSame('test', $parameters[0]);
}
return 'test';
});
$this->writer->expects($this->exactly(2))
->method('writeUserNotification');
$this->routeHelper->expects($this->exactly(2))
->method('getLinkCsv');
$this->helper->writeNotifications(Contact::NAME, 'test');
}
public function testNotificationSentToAdmins(): void
{
$this->helper->storeSummaryNotification('Foo', 'Bar', 1);
$this->helper->storeSummaryNotification('Bar', 'Foo', 2);
$matcher = $this->exactly(2);
$this->ownerProvider->expects($matcher)
->method('getOwnersForObjectIds')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(Contact::NAME, $parameters[0]);
$this->assertSame([1 => 1], $parameters[1]);
return [];
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(Contact::NAME, $parameters[0]);
$this->assertSame([2 => 2], $parameters[1]);
return [];
}
});
$this->userHelper->expects($this->exactly(2))
->method('getAdminUsers')
->willReturn([1]);
$matcher = $this->exactly(4);
$this->translator->expects($matcher)
->method('trans')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.header', $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('test', $parameters[0]);
}
if (3 === $matcher->numberOfInvocations()) {
$this->assertSame('mautic.integration.sync.user_notification.header', $parameters[0]);
}
if (4 === $matcher->numberOfInvocations()) {
$this->assertSame('test', $parameters[0]);
}
return 'test';
});
$this->writer->expects($this->exactly(2))
->method('writeUserNotification');
$this->routeHelper->expects($this->exactly(2))
->method('getLinkCsv');
$this->helper->writeNotifications(Contact::NAME, 'test');
}
public function testMoreThan25ObjectsResultInCountMessage(): void
{
$counter = 1;
$withIds = [];
do {
$this->helper->storeSummaryNotification('Foo', 'Bar', $counter);
$withIds[$counter] = $counter;
++$counter;
} while ($counter <= 26);
$this->ownerProvider->expects($this->once())
->method('getOwnersForObjectIds')
->with(Contact::NAME, $withIds)
->willReturn([]);
$this->userHelper->expects($this->once())
->method('getAdminUsers')
->willReturn([1]);
$this->translator->expects($this->exactly(2))
->method('trans')
->willReturnCallback(
function ($string, $params) {
$expectedStrings = [
'mautic.integration.sync.user_notification.header',
'mautic.integration.sync.user_notification.count_message',
];
if (!in_array($string, $expectedStrings)) {
$this->fail($string.' is not an expected translation key');
}
return $string;
}
);
$this->writer->expects($this->exactly(1))
->method('writeUserNotification');
$this->routeHelper->expects($this->never())
->method('getLinkCsv');
$this->helper->writeNotifications(Contact::NAME, 'test');
}
}

View File

@@ -0,0 +1,203 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Helper;
use Mautic\ChannelBundle\Helper\ChannelListHelper;
use Mautic\IntegrationsBundle\Event\MauticSyncFieldsLoadEvent;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use Mautic\IntegrationsBundle\Sync\VariableExpresser\VariableExpresserHelperInterface;
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
use Mautic\LeadBundle\Model\FieldModel;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
class FieldHelperTest extends TestCase
{
/**
* @var FieldModel&MockObject
*/
private MockObject $fieldModel;
/**
* @var MockObject&FieldsWithUniqueIdentifier
*/
private MockObject $fieldsWithUniqueIdentifier;
/**
* @var VariableExpresserHelperInterface&MockObject
*/
private MockObject $variableExpresserHelper;
/**
* @var ChannelListHelper&MockObject
*/
private MockObject $channelListHelper;
private MockObject $eventDispatcher;
/**
* @var MauticSyncFieldsLoadEvent&MockObject
*/
private MockObject $mauticSyncFieldsLoadEvent;
/**
* @var ObjectProvider&MockObject
*/
private MockObject $objectProvider;
private FieldHelper $fieldHelper;
protected function setUp(): void
{
$this->fieldModel = $this->createMock(FieldModel::class);
$this->variableExpresserHelper = $this->createMock(VariableExpresserHelperInterface::class);
$this->channelListHelper = $this->createMock(ChannelListHelper::class);
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->channelListHelper->method('getFeatureChannels')
->willReturn(['Email' => 'email']);
$this->mauticSyncFieldsLoadEvent = $this->createMock(MauticSyncFieldsLoadEvent::class);
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->eventDispatcher->method('dispatch')
->willReturn($this->mauticSyncFieldsLoadEvent);
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
$this->fieldHelper = new FieldHelper(
$this->fieldModel,
$this->fieldsWithUniqueIdentifier,
$this->variableExpresserHelper,
$this->channelListHelper,
$this->createMock(TranslatorInterface::class),
$this->eventDispatcher,
$this->objectProvider
);
}
public function testContactSyncFieldsReturned(): void
{
$objectName = Contact::NAME;
$syncFields = ['email' => 'Email'];
$this->mauticSyncFieldsLoadEvent->method('getObjectName')
->willReturn($objectName);
$this->mauticSyncFieldsLoadEvent->method('getFields')
->willReturn($syncFields);
$this->fieldModel->method('getFieldList')
->willReturn($syncFields);
$fields = $this->fieldHelper->getSyncFields($objectName);
$this->assertEquals(
[
'email',
'mautic_internal_contact_timeline',
'mautic_internal_dnc_email',
'mautic_internal_id',
],
array_keys($fields)
);
}
public function testCompanySyncFieldsReturned(): void
{
$objectName = Contact::NAME;
$syncFields = ['email' => 'Email'];
$this->mauticSyncFieldsLoadEvent->method('getObjectName')
->willReturn($objectName);
$this->mauticSyncFieldsLoadEvent->method('getFields')
->willReturn($syncFields);
$this->fieldModel->method('getFieldList')
->willReturn($syncFields);
$fields = $this->fieldHelper->getSyncFields($objectName);
$this->assertEquals(
[
'email',
'mautic_internal_contact_timeline',
'mautic_internal_dnc_email',
'mautic_internal_id',
],
array_keys($fields)
);
}
public function testGetRequiredFieldsForContact(): void
{
$this->fieldModel->expects($this->once())
->method('getFieldList')
->willReturn(['some fields']);
$this->fieldsWithUniqueIdentifier->expects($this->once())
->method('getFieldsWithUniqueIdentifier')
->willReturn(['some unique fields']);
$this->assertSame(
['some fields', 'some unique fields'],
$this->fieldHelper->getRequiredFields('lead')
);
// Call it for the second time to ensure the result was cached,
$this->assertSame(
['some fields', 'some unique fields'],
$this->fieldHelper->getRequiredFields('lead')
);
}
public function testGetRequiredFieldsForCompany(): void
{
$this->fieldModel->expects($this->once())
->method('getFieldList')
->willReturn(['some fields']);
$this->fieldsWithUniqueIdentifier->expects($this->never())
->method('getFieldsWithUniqueIdentifier');
$this->assertSame(
['some fields'],
$this->fieldHelper->getRequiredFields('company')
);
// Call it for the second time to ensure the result was cached,
$this->assertSame(
['some fields'],
$this->fieldHelper->getRequiredFields('company')
);
}
public function testGetFieldObjectName(): void
{
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Contact::NAME)
->willReturn(new Contact());
$this->assertSame(
Contact::ENTITY,
$this->fieldHelper->getFieldObjectName(Contact::NAME)
);
}
public function testGetNormalizedFieldType(): void
{
$this->assertEquals(NormalizedValueDAO::BOOLEAN_TYPE, $this->fieldHelper->getNormalizedFieldType('boolean'));
$this->assertEquals(NormalizedValueDAO::DATETIME_TYPE, $this->fieldHelper->getNormalizedFieldType('date'));
$this->assertEquals(NormalizedValueDAO::DATETIME_TYPE, $this->fieldHelper->getNormalizedFieldType('datetime'));
$this->assertEquals(NormalizedValueDAO::DATETIME_TYPE, $this->fieldHelper->getNormalizedFieldType('time'));
$this->assertEquals(NormalizedValueDAO::FLOAT_TYPE, $this->fieldHelper->getNormalizedFieldType('number'));
$this->assertEquals(NormalizedValueDAO::SELECT_TYPE, $this->fieldHelper->getNormalizedFieldType('select'));
$this->assertEquals(NormalizedValueDAO::MULTISELECT_TYPE, $this->fieldHelper->getNormalizedFieldType('multiselect'));
$this->assertEquals(NormalizedValueDAO::STRING_TYPE, $this->fieldHelper->getNormalizedFieldType('default'));
}
}

View File

@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\Executioner\Exception;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\Exception\FieldSchemaNotFoundException;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class FieldSchemaNotFoundExceptionTest extends TestCase
{
public function testMessage(): void
{
$object = 'SomeObject';
$alias = 'SomeAlias';
$exception = new FieldSchemaNotFoundException($object, $alias);
$expected = sprintf('Schema for alias "%s" of object "%s" not found', $alias, $object);
Assert::assertSame($expected, $exception->getMessage());
}
}

View File

@@ -0,0 +1,143 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\Executioner;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Notification\BulkNotification;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\FieldValidator;
use Mautic\LeadBundle\Entity\LeadFieldRepository;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class FieldValidatorTest extends TestCase
{
/**
* @var LeadFieldRepository&MockObject
*/
private MockObject $leadFieldRepository;
/**
* @var BulkNotification&MockObject
*/
private MockObject $bulkNotification;
private FieldValidator $fieldValidator;
protected function setup(): void
{
$this->leadFieldRepository = $this->createMock(LeadFieldRepository::class);
$this->bulkNotification = $this->createMock(BulkNotification::class);
$this->fieldValidator = new FieldValidator($this->leadFieldRepository, $this->bulkNotification);
}
public function testValidateFields(): void
{
$this->leadFieldRepository->method('getFieldSchemaData')
->willReturn([
'company' => [
'alias' => 'company',
'label' => 'Company',
'type' => 'text',
'isUniqueIdentifer' => false,
'charLengthLimit' => 5,
],
'email' => [
'alias' => 'email',
'label' => 'Email',
'type' => 'email',
'isUniqueIdentifer' => true,
'charLengthLimit' => 64,
],
'date' => [
'alias' => 'date',
'label' => 'Date',
'type' => 'date',
'isUniqueIdentifer' => false,
'charLengthLimit' => null,
],
'time' => [
'alias' => 'time',
'label' => 'Time',
'type' => 'time',
'isUniqueIdentifer' => false,
'charLengthLimit' => null,
],
'bool' => [
'alias' => 'bool',
'label' => 'Bool',
'type' => 'boolean',
'isUniqueIdentifer' => false,
'charLengthLimit' => null,
],
'number' => [
'alias' => 'number',
'label' => 'Number',
'type' => 'number',
'isUniqueIdentifer' => false,
'charLengthLimit' => null,
],
]);
$firstChangedObject = (new ObjectChangeDAO('integration', 'lead', '1', 'Lead', '00Q4H00000juXes'))
->addField(new FieldDAO('company', new NormalizedValueDAO('string', 'Some company', 'Some company')))
->addField(new FieldDAO('email', new NormalizedValueDAO('string', 'email@domain.tld', 'email@domain.tld')))
->addField(new FieldDAO('unknown', new NormalizedValueDAO('string', 'something', 'something')));
$secondChangedObject = (new ObjectChangeDAO('integration', 'lead', '1', 'Lead', '00Q4H00000juXes'))
->addField(new FieldDAO('date', new NormalizedValueDAO('date', '2020-09-08 10:05:35', '2020-09-08 10:05:35')))
->addField(new FieldDAO('time', new NormalizedValueDAO('date', '2020-09-08', '2020-09-08')))
->addField(new FieldDAO('number', new NormalizedValueDAO('url', 'https://url', 'https://url')))
->addField(new FieldDAO('bool', new NormalizedValueDAO('boolean', 1, true)));
$changedObjects = [
$firstChangedObject,
$secondChangedObject,
];
$matcher = $this->exactly(3);
$this->bulkNotification->expects($matcher)
->method('addNotification')
->willReturnCallback(function (...$parameters) use ($matcher, $firstChangedObject, $secondChangedObject) {
if (1 === $matcher->numberOfInvocations()) {
$this->getNotificationAssertion($parameters, "Custom field 'Company' with value 'Some company' exceeded maximum allowed length and was ignored during the sync. Your integration integration plugin may be configured improperly.", $firstChangedObject, 'company', 'length');
}
if (2 === $matcher->numberOfInvocations()) {
$this->getNotificationAssertion($parameters, "Custom field 'Time' of type 'time' did not match integration type 'date' and was ignored during the sync. Your integration integration plugin may be configured improperly.", $secondChangedObject, 'time', 'type');
}
if (3 === $matcher->numberOfInvocations()) {
$this->getNotificationAssertion($parameters, "Custom field 'Number' of type 'number' did not match integration type 'url' and was ignored during the sync. Your integration integration plugin may be configured improperly.", $secondChangedObject, 'number', 'type');
}
});
$this->bulkNotification->expects($this->once())
->method('flush');
$this->fieldValidator->validateFields('lead', $changedObjects);
Assert::assertNull($firstChangedObject->getField('company'));
Assert::assertInstanceOf(FieldDAO::class, $firstChangedObject->getField('email'));
Assert::assertInstanceOf(FieldDAO::class, $firstChangedObject->getField('unknown'));
Assert::assertInstanceOf(FieldDAO::class, $secondChangedObject->getField('date'));
Assert::assertNull($secondChangedObject->getField('time'));
Assert::assertNull($secondChangedObject->getField('number'));
Assert::assertInstanceOf(FieldDAO::class, $secondChangedObject->getField('bool'));
}
/**
* @param mixed[] $parameters
*/
private function getNotificationAssertion(array $parameters, string $message, ObjectChangeDAO $changedObject, string $fieldName, string $type): void
{
Assert::assertSame($parameters[0], $changedObject->getIntegration().'-'.$changedObject->getObject().'-'.$fieldName.'-'.$type);
Assert::assertSame($parameters[1], $message);
Assert::assertSame($parameters[2], $changedObject->getIntegration());
Assert::assertSame($parameters[3], sprintf('%s %s', $changedObject->getMappedObjectId(), $changedObject->getObject()));
Assert::assertSame($parameters[4], $changedObject->getObject());
Assert::assertSame($parameters[5], 0);
Assert::assertSame($parameters[6], sprintf('%s %s %s', $changedObject->getIntegration(), $changedObject->getObject(), $changedObject->getMappedObjectId()));
}
}

View File

@@ -0,0 +1,440 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\Executioner;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Event\InternalObjectCreateEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectUpdateEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\UpdatedObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\OrderDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use Mautic\IntegrationsBundle\Sync\Helper\MappingHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\FieldValidatorInterface;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\OrderExecutioner;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\ReferenceResolverInterface;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\ObjectInterface;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class OrderExecutionerTest extends TestCase
{
private const INTEGRATION_NAME = 'Test';
/**
* @var MappingHelper|MockObject
*/
private MockObject $mappingHelper;
/**
* @var EventDispatcherInterface|MockObject
*/
private MockObject $dispatcher;
/**
* @var ObjectProvider|MockObject
*/
private MockObject $objectProvider;
private OrderExecutioner $orderExecutioner;
/**
* @var ReferenceResolverInterface|MockObject
*/
private MockObject $referenceResolver;
/**
* @var FieldValidatorInterface|MockObject
*/
private MockObject $fieldValidator;
protected function setup(): void
{
$this->mappingHelper = $this->createMock(MappingHelper::class);
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->referenceResolver = $this->createMock(ReferenceResolverInterface::class);
$this->fieldValidator = $this->createMock(FieldValidatorInterface::class);
$this->orderExecutioner = new OrderExecutioner(
$this->mappingHelper,
$this->dispatcher,
$this->objectProvider,
$this->referenceResolver,
$this->fieldValidator
);
}
public function testContactsAreUpdatedAndCreated(): void
{
$this->objectProvider->expects($this->exactly(2))
->method('getObjectByName')
->with(Contact::NAME)
->willReturn(new Contact());
$matcher = $this->exactly(2);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectUpdateEvent $event) {
Assert::assertSame(Contact::NAME, $event->getObject()->getName());
Assert::assertSame([1, 2], $event->getIdentifiedObjectIds());
Assert::assertCount(2, $event->getUpdateObjects());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectCreateEvent $event) {
Assert::assertSame(Contact::NAME, $event->getObject()->getName());
Assert::assertCount(1, $event->getCreateObjects());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS, $parameters[1]);
}
return $parameters[0];
});
$this->mappingHelper->expects($this->exactly(1))
->method('updateObjectMappings');
$this->mappingHelper->expects($this->exactly(1))
->method('saveObjectMappings');
$this->referenceResolver->expects($this->exactly(2))
->method('resolveReferences');
$this->fieldValidator->expects($this->exactly(2))
->method('validateFields');
$this->orderExecutioner->execute($this->getSyncOrder(Contact::NAME));
}
public function testUpdatedObjectsWithoutAnObjectMappingDoesNotGetAddedToObjectMappingsDAO(): void
{
$this->objectProvider->expects($this->exactly(2))
->method('getObjectByName')
->with(Contact::NAME)
->willReturn(new Contact());
$matcher = $this->exactly(2);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectUpdateEvent $event) {
Assert::assertSame(Contact::NAME, $event->getObject()->getName());
Assert::assertSame([1, 2], $event->getIdentifiedObjectIds());
Assert::assertCount(2, $event->getUpdateObjects());
$updatedObjectMappings = [];
foreach ($event->getUpdateObjects() as $key => $updateObject) {
$updatedObjectMappings[] = $updatedObjectMapping = new UpdatedObjectMappingDAO(
$updateObject->getIntegration(),
$updateObject->getObject(),
$updateObject->getObjectId(),
new \DateTime()
);
if (0 !== $key) {
// Only inject an object mapping for one of the objects
break;
}
$objectMapping = new ObjectMapping();
$objectMapping->setIntegration($updateObject->getIntegration());
$objectMapping->setIntegrationObjectName($updateObject->getObject());
$objectMapping->setIntegrationObjectId($updateObject->getObjectId());
$updatedObjectMapping->setObjectMapping($objectMapping);
}
$event->setUpdatedObjectMappings($updatedObjectMappings);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectCreateEvent $event) {
Assert::assertSame(Contact::NAME, $event->getObject()->getName());
Assert::assertCount(1, $event->getCreateObjects());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS, $parameters[1]);
}
return $parameters[0];
});
$this->mappingHelper->expects($this->exactly(1))
->method('updateObjectMappings');
$this->mappingHelper->expects($this->exactly(1))
->method('saveObjectMappings');
$this->referenceResolver->expects($this->exactly(2))
->method('resolveReferences');
$this->fieldValidator->expects($this->exactly(2))
->method('validateFields');
$this->orderExecutioner->execute($this->getSyncOrder(Contact::NAME));
}
public function testCompaniesAreUpdatedAndCreated(): void
{
$this->objectProvider->expects($this->exactly(2))
->method('getObjectByName')
->with(Company::NAME)
->willReturn(new Company());
$matcher = $this->exactly(2);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectUpdateEvent $event) {
Assert::assertSame(Company::NAME, $event->getObject()->getName());
Assert::assertSame([1, 2], $event->getIdentifiedObjectIds());
Assert::assertCount(2, $event->getUpdateObjects());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectCreateEvent $event) {
Assert::assertSame(Company::NAME, $event->getObject()->getName());
Assert::assertCount(1, $event->getCreateObjects());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS, $parameters[1]);
}
return $parameters[0];
});
$this->mappingHelper->expects($this->exactly(1))
->method('updateObjectMappings');
$this->mappingHelper->expects($this->exactly(1))
->method('saveObjectMappings');
$this->referenceResolver->expects($this->exactly(2))
->method('resolveReferences');
$this->fieldValidator->expects($this->exactly(2))
->method('validateFields');
$syncOrder = $this->getSyncOrder(Company::NAME);
$this->orderExecutioner->execute($syncOrder);
}
public function testMixedObjectsAreUpdatedAndCreated(): void
{
$matcher = $this->exactly(4);
$this->objectProvider->expects($matcher)
->method('getObjectByName')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(Contact::NAME, $parameters[0]);
return new Contact();
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(Company::NAME, $parameters[0]);
return new Company();
}
if (3 === $matcher->numberOfInvocations()) {
$this->assertSame(Contact::NAME, $parameters[0]);
return new Contact();
}
if (4 === $matcher->numberOfInvocations()) {
$this->assertSame(Company::NAME, $parameters[0]);
return new Company();
}
});
$matcher = $this->exactly(4);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectUpdateEvent $event) {
Assert::assertSame(Contact::NAME, $event->getObject()->getName());
$updatedObjectMappings = [];
foreach ($event->getUpdateObjects() as $updateObject) {
$updatedObjectMappings[] = $updatedObjectMapping = new UpdatedObjectMappingDAO(
$updateObject->getIntegration(),
$updateObject->getObject(),
$updateObject->getObjectId(),
new \DateTime()
);
$objectMapping = new ObjectMapping();
$objectMapping->setIntegration($updateObject->getIntegration());
$objectMapping->setIntegrationObjectName($updateObject->getObject());
$objectMapping->setIntegrationObjectId($updateObject->getObjectId());
$updatedObjectMapping->setObjectMapping($objectMapping);
}
$event->setUpdatedObjectMappings($updatedObjectMappings);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectUpdateEvent $event) {
Assert::assertSame(Company::NAME, $event->getObject()->getName());
$updatedObjectMappings = [];
foreach ($event->getUpdateObjects() as $updateObject) {
$updatedObjectMappings[] = $updatedObjectMapping = new UpdatedObjectMappingDAO(
$updateObject->getIntegration(),
$updateObject->getObject(),
$updateObject->getObjectId(),
new \DateTime()
);
$objectMapping = new ObjectMapping();
$objectMapping->setIntegration($updateObject->getIntegration());
$objectMapping->setIntegrationObjectName($updateObject->getObject());
$objectMapping->setIntegrationObjectId($updateObject->getObjectId());
$updatedObjectMapping->setObjectMapping($objectMapping);
}
$event->setUpdatedObjectMappings($updatedObjectMappings);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS, $parameters[1]);
}
if (3 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectCreateEvent $event) {
Assert::assertSame(Contact::NAME, $event->getObject()->getName());
$createdObjectMappings = [];
foreach ($event->getCreateObjects() as $createObject) {
$objectMapping = new ObjectMapping();
$objectMapping->setIntegration($createObject->getIntegration());
$objectMapping->setIntegrationObjectName($createObject->getObject());
$objectMapping->setIntegrationObjectId($createObject->getObjectId());
$createdObjectMappings[] = $objectMapping;
}
$event->setObjectMappings($createdObjectMappings);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS, $parameters[1]);
}
if (4 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectCreateEvent $event) {
Assert::assertSame(Company::NAME, $event->getObject()->getName());
$createdObjectMappings = [];
foreach ($event->getCreateObjects() as $createObject) {
$objectMapping = new ObjectMapping();
$objectMapping->setIntegration($createObject->getIntegration());
$objectMapping->setIntegrationObjectName($createObject->getObject());
$objectMapping->setIntegrationObjectId($createObject->getObjectId());
$createdObjectMappings[] = $objectMapping;
}
$event->setObjectMappings($createdObjectMappings);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS, $parameters[1]);
}
return $parameters[0];
});
$this->mappingHelper->expects($this->exactly(2))
->method('updateObjectMappings');
$this->mappingHelper->expects($this->exactly(2))
->method('saveObjectMappings');
// Merge companies and contacts for the test
$syncOrder = $this->getSyncOrder(Contact::NAME);
$companySyncOrder = $this->getSyncOrder(Company::NAME);
foreach ($companySyncOrder->getChangedObjectsByObjectType(Company::NAME) as $objectChange) {
$syncOrder->addObjectChange($objectChange);
}
$orderMappings = $this->orderExecutioner->execute($syncOrder);
Assert::assertCount(4, $orderMappings->getUpdatedMappings());
Assert::assertCount(2, $orderMappings->getNewMappings());
}
public function testEmptyObjectsForUpdateDoesNothing(): void
{
$syncOrder = new OrderDAO(new \DateTimeImmutable(), false, self::INTEGRATION_NAME);
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, 'bar', null, 'bar', 4));
$this->dispatcher->expects($this->once())
->method('dispatch')
->with($this->isInstanceOf(InternalObjectCreateEvent::class), IntegrationEvents::INTEGRATION_CREATE_INTERNAL_OBJECTS);
$this->orderExecutioner->execute($syncOrder);
}
public function testEmptyObjectsForCreateDoesNothing(): void
{
$syncOrder = new OrderDAO(new \DateTimeImmutable(), false, self::INTEGRATION_NAME);
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, 'bar', 4, 'bar', 4));
$this->dispatcher->expects($this->once())
->method('dispatch')
->with($this->isInstanceOf(InternalObjectUpdateEvent::class), IntegrationEvents::INTEGRATION_UPDATE_INTERNAL_OBJECTS);
$this->orderExecutioner->execute($syncOrder);
}
public function testObjectNotFoundExceptionIsLoggedAndNothingElse(): void
{
$syncOrder = $this->getSyncOrder('foo');
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, 'bar', 4, 'bar', 4));
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, 'bar', null, 'bar', 4));
// update and create per object
$this->objectProvider->expects($this->exactly(4))
->method('getObjectByName')
->willReturnCallback(
function (string $objectName) {
if ('bar' === $objectName) {
throw new ObjectNotFoundException($objectName);
}
return $this->createMock(ObjectInterface::class);
}
);
// only foo should recognized and processed
$this->dispatcher->expects($this->exactly(2))
->method('dispatch');
$this->orderExecutioner->execute($syncOrder);
}
/**
* @throws \Exception
*/
private function getSyncOrder(string $objectName): OrderDAO
{
$syncOrder = new OrderDAO(new \DateTimeImmutable(), false, self::INTEGRATION_NAME);
// Two updates
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, $objectName, 1, $objectName, 1));
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, $objectName, 2, $objectName, 2));
// One create
$syncOrder->addObjectChange(new ObjectChangeDAO(self::INTEGRATION_NAME, $objectName, null, $objectName, 3));
return $syncOrder;
}
}

View File

@@ -0,0 +1,116 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\Executioner;
use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Query\QueryBuilder;
use Doctrine\DBAL\Result;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\ReferenceValueDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\ReferenceResolver;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ReferenceResolverTest extends TestCase
{
/**
* @var Connection|MockObject
*/
private MockObject $connection;
private ReferenceResolver $referenceResolver;
protected function setup(): void
{
$this->connection = $this->createMock(Connection::class);
$this->referenceResolver = new ReferenceResolver($this->connection);
}
public function testResolveLeadReferences(): void
{
$this->connection->method('createQueryBuilder')
->willReturn($this->createQueryBuilder('Company name', false));
$companyReference = $this->createReference('company', 3);
$userReference = $this->createReference('user', 4);
$notFoundReference = $this->createReference('company', 5);
$changedObject = (new ObjectChangeDAO('integration', 'lead', '1', 'Lead', '00Q4H00000juXes'))
->addField(new FieldDAO('company', new NormalizedValueDAO('reference', $companyReference, $companyReference)))
->addField(new FieldDAO('user', new NormalizedValueDAO('reference', $userReference, $userReference)))
->addField(new FieldDAO('city', new NormalizedValueDAO('text', 'Some city', 'Some city')))
->addField(new FieldDAO('manager', new NormalizedValueDAO('reference', $notFoundReference, $notFoundReference)));
$this->referenceResolver->resolveReferences('lead', [$changedObject]);
$companyField = $changedObject->getField('company');
Assert::assertInstanceOf(FieldDAO::class, $companyField);
Assert::assertSame('Company name', $companyField->getValue()->getOriginalValue());
Assert::assertSame('Company name', $companyField->getValue()->getNormalizedValue());
$userField = $changedObject->getField('user');
Assert::assertInstanceOf(FieldDAO::class, $userField);
Assert::assertNull($userField->getValue()->getOriginalValue());
Assert::assertNull($userField->getValue()->getNormalizedValue());
$cityField = $changedObject->getField('city');
Assert::assertInstanceOf(FieldDAO::class, $cityField);
Assert::assertSame('Some city', $cityField->getValue()->getOriginalValue());
Assert::assertSame('Some city', $cityField->getValue()->getNormalizedValue());
$managerField = $changedObject->getField('manager');
Assert::assertInstanceOf(FieldDAO::class, $managerField);
Assert::assertNull($managerField->getValue()->getOriginalValue());
Assert::assertNull($managerField->getValue()->getNormalizedValue());
}
public function testResolveCompanyReferences(): void
{
$this->connection->method('createQueryBuilder')
->willReturn($this->createQueryBuilder('Company name'));
$companyReference = $this->createReference('company', 3);
$changedObject = (new ObjectChangeDAO('integration', 'company', '1', 'Lead', '00Q4H00000juXes'))
->addField(new FieldDAO('company', new NormalizedValueDAO('reference', $companyReference, $companyReference)));
$this->referenceResolver->resolveReferences('company', [$changedObject]);
$companyField = $changedObject->getField('company');
Assert::assertInstanceOf(FieldDAO::class, $companyField);
Assert::assertSame($companyReference, $companyField->getValue()->getOriginalValue());
Assert::assertSame($companyReference, $companyField->getValue()->getNormalizedValue());
}
private function createReference(string $type, int $value): ReferenceValueDAO
{
$reference = new ReferenceValueDAO();
$reference->setType($type);
$reference->setValue($value);
return $reference;
}
/**
* @param mixed ...$returnValues
*
* @return QueryBuilder|MockObject
*/
private function createQueryBuilder(...$returnValues)
{
$result = $this->createMock(Result::class);
$result->method('fetchOne')
->willReturnOnConsecutiveCalls(...$returnValues);
$queryBuilder = $this->createMock(QueryBuilder::class);
$queryBuilder->method('executeQuery')
->willReturn($result);
return $queryBuilder;
}
}

View File

@@ -0,0 +1,271 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\ObjectHelper;
use Doctrine\DBAL\Connection;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company as CompanyObject;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\CompanyObjectHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\CompanyRepository;
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
use Mautic\LeadBundle\Model\CompanyModel;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class CompanyObjectHelperTest extends TestCase
{
/**
* @var CompanyModel&MockObject
*/
private MockObject $model;
/**
* @var CompanyRepository&MockObject
*/
private MockObject $repository;
/**
* @var Connection&MockObject
*/
private MockObject $connection;
/**
* @var FieldsWithUniqueIdentifier&MockObject
*/
private MockObject $fieldsWithUniqueIdentifier;
protected function setUp(): void
{
$this->model = $this->createMock(CompanyModel::class);
$this->repository = $this->createMock(CompanyRepository::class);
$this->connection = $this->createMock(Connection::class);
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
$this->fieldsWithUniqueIdentifier->method('getFieldsWithUniqueIdentifier')
->with(['object' => CompanyObject::NAME])
->willReturn(
[
'companyemail' => [],
]
);
}
public function testCreateWithDuplicateUniqueIdentifiers(): void
{
$idMap = [
'email1@email.com' => 127,
'email2@email.com' => 128,
];
$this->model->expects($this->exactly(3))
->method('saveEntity')
->with(
$this->callback(function (Company $company) use ($idMap): bool {
// Set ID
$reflection = new \ReflectionClass($company);
$property = $reflection->getProperty('id');
$property->setAccessible(true);
$property->setValue($company, $idMap[$company->getEmail()]);
return true;
})
);
$this->repository->expects($this->exactly(2))
->method('detachEntity');
// Test that two objects with the same unique identifier are merged into one
$object1 = $this->getObject(1, ['companyemail' => 'email1@email.com']);
$object2 = $this->getObject(2, ['companyemail' => 'email2@email.com']);
$object3 = $this->getObject(3, ['companyemail' => 'email1@email.com']);
$objects = [$object1, $object2, $object3];
$objectMappings = $this->getObjectHelper()->create($objects);
foreach ($objectMappings as $key => $objectMapping) {
$this->assertEquals('Test', $objectMapping->getIntegration());
$this->assertEquals(CompanyObject::NAME, $objectMapping->getInternalObjectName());
$this->assertEquals('MappedObject', $objectMapping->getIntegrationObjectName());
$this->assertEquals($objects[$key]->getMappedObjectId(), $objectMapping->getIntegrationObjectId());
// Test that mapped ID matches internal ID
switch ($objects[$key]->getMappedObjectId()) {
case 1:
case 3:
Assert::assertSame(127, $objectMapping->getInternalObjectId());
break;
case 2:
Assert::assertSame(128, $objectMapping->getInternalObjectId());
break;
}
}
}
public function testCreateWithOneWithoutUniqueIdentifier(): void
{
$idMap = [
'email1@email.com' => 127,
'email2@email.com' => 128,
'' => 129,
];
$this->model->expects($this->exactly(4))
->method('saveEntity')
->with(
$this->callback(function (Company $company) use ($idMap): bool {
// Set ID
$reflection = new \ReflectionClass($company);
$property = $reflection->getProperty('id');
$property->setAccessible(true);
$property->setValue($company, $idMap[$company->getEmail()]);
return true;
})
);
$this->repository->expects($this->exactly(3))
->method('detachEntity');
// Test that two objects with the same unique identifier are merged into one
$object1 = $this->getObject(1, ['companyemail' => 'email1@email.com']);
$object2 = $this->getObject(2, ['companyemail' => 'email2@email.com']);
$object3 = $this->getObject(3, ['companyemail' => 'email1@email.com']);
$object4 = $this->getObject(4, ['companyname' => 'Some Biz']);
$objects = [$object1, $object2, $object3, $object4];
$objectMappings = $this->getObjectHelper()->create($objects);
foreach ($objectMappings as $key => $objectMapping) {
$this->assertEquals('Test', $objectMapping->getIntegration());
$this->assertEquals(CompanyObject::NAME, $objectMapping->getInternalObjectName());
$this->assertEquals('MappedObject', $objectMapping->getIntegrationObjectName());
$this->assertEquals($objects[$key]->getMappedObjectId(), $objectMapping->getIntegrationObjectId());
// Test that mapped ID matches internal ID
switch ($objects[$key]->getMappedObjectId()) {
case 1:
case 3:
Assert::assertSame(127, $objectMapping->getInternalObjectId());
break;
case 2:
Assert::assertSame(128, $objectMapping->getInternalObjectId());
break;
case 4:
Assert::assertSame(129, $objectMapping->getInternalObjectId());
break;
}
}
}
public function testUpdate(): void
{
$this->model->expects($this->exactly(2))
->method('saveEntity');
$this->repository->expects($this->exactly(2))
->method('detachEntity');
$objects = [
0 => new ObjectChangeDAO('Test', MauticSyncDataExchange::OBJECT_COMPANY, 0, 'MappedObject', 0, new \DateTime()),
1 => new ObjectChangeDAO('Test', MauticSyncDataExchange::OBJECT_COMPANY, 1, 'MappedObject', 1, new \DateTime()),
];
$company1 = $this->createMock(Company::class);
$company1->method('getId')
->willReturn(0);
$company2 = $this->createMock(Company::class);
$company2->method('getId')
->willReturn(1);
$this->model->expects($this->once())
->method('getEntities')
->willReturn(
[
$company1,
$company2,
]
);
$objectMappings = $this->getObjectHelper()->update([3, 4], $objects);
foreach ($objectMappings as $objectMapping) {
$this->assertEquals('Test', $objectMapping->getIntegration());
$this->assertEquals('MappedObject', $objectMapping->getIntegrationObjectName());
$this->assertTrue(isset($objects[$objectMapping->getIntegrationObjectId()]));
$this->assertEquals($objects[$objectMapping->getIntegrationObjectId()]->getMappedObjectId(), $objectMapping->getIntegrationObjectId());
}
}
public function testFindObjectById(): void
{
$company = new Company();
$this->repository->expects(self::once())
->method('getEntity')
->with(1)
->willReturn($company);
self::assertSame($company, $this->getObjectHelper()->findObjectById(1));
}
public function testFindObjectByIdReturnsNull(): void
{
$this->repository->expects(self::once())
->method('getEntity')
->with(1);
self::assertNull($this->getObjectHelper()->findObjectById(1));
}
public function testSetFieldValues(): void
{
$company = new Company();
$this->model->expects(self::once())
->method('setFieldValues')
->with($company, []);
$this->getObjectHelper()->setFieldValues($company);
}
public function testUpdateEmpty(): void
{
$this->model->expects($this->never())
->method('getEntities');
$objectMappings = $this->getObjectHelper()->update([], []);
Assert::assertSame([], $objectMappings);
}
private function getObjectHelper(): CompanyObjectHelper
{
return new CompanyObjectHelper($this->model, $this->repository, $this->connection, $this->fieldsWithUniqueIdentifier);
}
/**
* @param array<string,string> $fieldValues
*/
private function getObject(int $mappedId, array $fieldValues): ObjectChangeDAO
{
$object = new ObjectChangeDAO(
'Test',
CompanyObject::NAME,
null,
'MappedObject',
$mappedId,
new \DateTime()
);
foreach ($fieldValues as $name => $value) {
$object->addField(
new FieldDAO($name, new NormalizedValueDAO('string', $value))
);
}
return $object;
}
}

View File

@@ -0,0 +1,391 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\ObjectHelper;
use Doctrine\DBAL\Connection;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\ReferenceValueDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\ContactObjectHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\LeadBundle\DataObject\LeadManipulator;
use Mautic\LeadBundle\Entity\Lead;
use Mautic\LeadBundle\Entity\LeadRepository;
use Mautic\LeadBundle\Exception\ImportFailedException;
use Mautic\LeadBundle\Field\FieldList;
use Mautic\LeadBundle\Field\FieldsWithUniqueIdentifier;
use Mautic\LeadBundle\Model\DoNotContact;
use Mautic\LeadBundle\Model\LeadModel;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ContactObjectHelperTest extends TestCase
{
/**
* @var LeadModel&MockObject
*/
private MockObject $model;
/**
* @var LeadRepository&MockObject
*/
private MockObject $repository;
/**
* @var Connection&MockObject
*/
private MockObject $connection;
/**
* @var DoNotContact&MockObject
*/
private MockObject $doNotContactModel;
/**
* @var FieldList&MockObject
*/
private MockObject $fieldList;
/**
* @var FieldsWithUniqueIdentifier&MockObject
*/
private MockObject $fieldsWithUniqueIdentifier;
protected function setUp(): void
{
$this->model = $this->createMock(LeadModel::class);
$this->repository = $this->createMock(LeadRepository::class);
$this->connection = $this->createMock(Connection::class);
$this->doNotContactModel = $this->createMock(DoNotContact::class);
$this->fieldList = $this->createMock(FieldList::class);
$this->fieldsWithUniqueIdentifier = $this->createMock(FieldsWithUniqueIdentifier::class);
$this->fieldList->method('getFieldList')
->willReturn(
[
'email' => [],
'company' => [],
]
);
$this->fieldsWithUniqueIdentifier->method('getFieldsWithUniqueIdentifier')
->with(['object' => Contact::NAME])
->willReturn(
[
'email' => [],
]
);
}
public function testCreateWithDuplicateUniqueIdentifiers(): void
{
$idMap = [
'email1@email.com' => 127,
'email2@email.com' => 128,
];
$this->model->expects($this->exactly(3))
->method('saveEntity')
->with(
$this->callback(function (Lead $lead) use ($idMap): bool {
$this->assertManipulator($lead, 'create');
// Set contact ID
$reflection = new \ReflectionClass($lead);
$property = $reflection->getProperty('id');
$property->setAccessible(true);
$property->setValue($lead, $idMap[$lead->getEmail()]);
return true;
})
);
$this->repository->expects($this->exactly(2))
->method('detachEntity');
// Test that two objects with the same unique identifier are merged into one
$object1 = $this->getObject(1, ['email' => 'email1@email.com']);
$object2 = $this->getObject(2, ['email' => 'email2@email.com']);
$object3 = $this->getObject(3, ['email' => 'email1@email.com']);
$objects = [$object1, $object2, $object3];
$objectMappings = $this->getObjectHelper()->create($objects);
foreach ($objectMappings as $key => $objectMapping) {
$this->assertEquals('Test', $objectMapping->getIntegration());
$this->assertEquals(Contact::NAME, $objectMapping->getInternalObjectName());
$this->assertEquals('MappedObject', $objectMapping->getIntegrationObjectName());
$this->assertEquals($objects[$key]->getMappedObjectId(), $objectMapping->getIntegrationObjectId());
// Test that mapped ID matches internal ID
switch ($objects[$key]->getMappedObjectId()) {
case 1:
case 3:
Assert::assertSame(127, $objectMapping->getInternalObjectId());
break;
case 2:
Assert::assertSame(128, $objectMapping->getInternalObjectId());
break;
}
}
}
public function testCreateWithOneWithoutUniqueIdentifier(): void
{
$idMap = [
'email1@email.com' => 127,
'email2@email.com' => 128,
'' => 129,
];
$this->model->expects($this->exactly(4))
->method('saveEntity')
->with(
$this->callback(function (Lead $lead) use ($idMap): bool {
$this->assertManipulator($lead, 'create');
// Set contact ID
$reflection = new \ReflectionClass($lead);
$property = $reflection->getProperty('id');
$property->setAccessible(true);
$property->setValue($lead, $idMap[$lead->getEmail()]);
return true;
})
);
$this->repository->expects($this->exactly(3))
->method('detachEntity');
// Test that two objects with the same unique identifier are merged into one
$object1 = $this->getObject(1, ['email' => 'email1@email.com']);
$object2 = $this->getObject(2, ['email' => 'email2@email.com']);
$object3 = $this->getObject(3, ['email' => 'email1@email.com']);
$object4 = $this->getObject(4, ['firstname' => 'Somebody']);
$objects = [$object1, $object2, $object3, $object4];
$objectMappings = $this->getObjectHelper()->create($objects);
foreach ($objectMappings as $key => $objectMapping) {
$this->assertEquals('Test', $objectMapping->getIntegration());
$this->assertEquals(Contact::NAME, $objectMapping->getInternalObjectName());
$this->assertEquals('MappedObject', $objectMapping->getIntegrationObjectName());
$this->assertEquals($objects[$key]->getMappedObjectId(), $objectMapping->getIntegrationObjectId());
// Test that mapped ID matches internal ID
switch ($objects[$key]->getMappedObjectId()) {
case 1:
case 3:
Assert::assertSame(127, $objectMapping->getInternalObjectId());
break;
case 2:
Assert::assertSame(128, $objectMapping->getInternalObjectId());
break;
case 4:
Assert::assertSame(129, $objectMapping->getInternalObjectId());
break;
}
}
}
public function testUpdate(): void
{
$this->model->expects($this->exactly(2))
->method('saveEntity');
$this->repository->expects($this->exactly(2))
->method('detachEntity');
$objectChangeDaoA = new ObjectChangeDAO('Test', Contact::NAME, 0, 'MappedObject', 0, new \DateTime());
$objectChangeDaoB = new ObjectChangeDAO('Test', Contact::NAME, 1, 'MappedObject', 1, new \DateTime());
$objects = [$objectChangeDaoA, $objectChangeDaoB];
$companyId = 1234;
$companyValue = new ReferenceValueDAO();
$companyValue->setValue($companyId);
$companyValue->setType(MauticSyncDataExchange::OBJECT_COMPANY);
$emailField = new FieldDAO('email', new NormalizedValueDAO('email', 'john@doe.com'));
$companyField = new FieldDAO(
MauticSyncDataExchange::OBJECT_COMPANY,
new NormalizedValueDAO('reference', $companyValue, 'Company A')
);
$objectChangeDaoA->addField($emailField);
$objectChangeDaoA->addField($companyField);
$contact1 = $this->createPartialMock(Lead::class, ['getId', 'addUpdatedField']);
$contact1->method('getId')
->willReturn(0);
$contact2 = $this->createPartialMock(Lead::class, ['getId']);
$contact2->method('getId')
->willReturn(1);
$this->model->expects($this->once())
->method('getEntities')
->willReturn(
[
$contact1,
$contact2,
]
);
$matcher = $this->exactly(2);
$contact1->expects($matcher)
->method('addUpdatedField')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('email', $parameters[0]);
$this->assertSame('john@doe.com', $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(MauticSyncDataExchange::OBJECT_COMPANY, $parameters[0]);
$this->assertSame('Company A', $parameters[1]);
}
});
$objectMappings = $this->getObjectHelper()->update([3, 4], $objects);
foreach ($objectMappings as $objectMapping) {
$this->assertEquals('Test', $objectMapping->getIntegration());
$this->assertEquals('MappedObject', $objectMapping->getIntegrationObjectName());
$this->assertTrue(isset($objects[$objectMapping->getIntegrationObjectId()]));
$this->assertEquals($objects[$objectMapping->getIntegrationObjectId()]->getMappedObjectId(), $objectMapping->getIntegrationObjectId());
}
$this->assertManipulator($contact1, 'update');
$this->assertManipulator($contact2, 'update');
}
public function testDoNotContactIsAdded(): void
{
$this->doNotContactModel->expects($this->once())
->method('addDncForContact')
->with(1, 'email', 1, 'Test', true, true, true);
$objectChangeDAO = new ObjectChangeDAO('Test', Contact::NAME, 1, 'MappedObject', 1, new \DateTime());
$objectChangeDAO->addField(new FieldDAO('mautic_internal_dnc_email', new NormalizedValueDAO(NormalizedValueDAO::INT_TYPE, 1)));
$objects = [
1 => $objectChangeDAO,
];
$contact1 = $this->createMock(Lead::class);
$contact1->method('getId')
->willReturn(1);
$this->model->expects($this->once())
->method('getEntities')
->willReturn([$contact1]);
$this->getObjectHelper()->update([1], $objects);
}
public function testDoNotContactIsRemoved(): void
{
$this->doNotContactModel->expects($this->once())
->method('removeDncForContact')
->with(1, 'email');
$objectChangeDAO = new ObjectChangeDAO('Test', Contact::NAME, 1, 'MappedObject', 1, new \DateTime());
$objectChangeDAO->addField(new FieldDAO('mautic_internal_dnc_email', new NormalizedValueDAO(NormalizedValueDAO::INT_TYPE, 0)));
$objects = [
1 => $objectChangeDAO,
];
$contact1 = $this->createMock(Lead::class);
$contact1->method('getId')
->willReturn(1);
$this->model->expects($this->once())
->method('getEntities')
->willReturn([$contact1]);
$this->getObjectHelper()->update([1], $objects);
}
public function testUnrecognizedDoNotContactDefaultsToManualDNC(): void
{
$this->doNotContactModel->expects($this->once())
->method('addDncForContact')
->with(1, 'email', 3, 'Test', true, true, true);
$objectChangeDAO = new ObjectChangeDAO('Test', Contact::NAME, 1, 'MappedObject', 1, new \DateTime());
$objectChangeDAO->addField(new FieldDAO('mautic_internal_dnc_email', new NormalizedValueDAO(NormalizedValueDAO::INT_TYPE, 4)));
$objects = [
1 => $objectChangeDAO,
];
$contact1 = $this->createMock(Lead::class);
$contact1->method('getId')
->willReturn(1);
$this->model->expects($this->once())
->method('getEntities')
->willReturn([$contact1]);
$this->getObjectHelper()->update([1], $objects);
}
public function testFindObjectById(): void
{
$contact = new Lead();
$this->repository->expects(self::once())
->method('getEntity')
->with(1)
->willReturn($contact);
self::assertSame($contact, $this->getObjectHelper()->findObjectById(1));
}
public function testFindObjectByIdReturnsNull(): void
{
$this->repository->expects(self::once())
->method('getEntity')
->with(1);
self::assertNull($this->getObjectHelper()->findObjectById(1));
}
/**
* @throws ImportFailedException
*/
public function testSetFieldValues(): void
{
$contact = new Lead();
$this->model->expects(self::once())
->method('setFieldValues')
->with($contact, []);
$this->getObjectHelper()->setFieldValues($contact);
}
private function getObjectHelper(): ContactObjectHelper
{
return new ContactObjectHelper($this->model, $this->repository, $this->connection, $this->doNotContactModel, $this->fieldList, $this->fieldsWithUniqueIdentifier);
}
private function assertManipulator(Lead $lead, string $objectName): void
{
$manipulator = $lead->getManipulator();
$this->assertInstanceOf(LeadManipulator::class, $manipulator);
$this->assertSame('integrations', $manipulator->getBundleName());
$this->assertSame($objectName, $manipulator->getObjectName());
}
/**
* @param array<string,string> $fieldValues
*/
private function getObject(int $mappedId, array $fieldValues): ObjectChangeDAO
{
$object = new ObjectChangeDAO('Test', Contact::NAME, null, 'MappedObject', $mappedId, new \DateTime());
foreach ($fieldValues as $name => $value) {
$object->addField(
new FieldDAO($name, new NormalizedValueDAO('string', $value))
);
}
return $object;
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal;
use Mautic\IntegrationsBundle\Event\InternalObjectEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class ObjectProviderTest extends TestCase
{
/**
* @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $dispatcher;
private ObjectProvider $objectProvider;
protected function setUp(): void
{
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->objectProvider = new ObjectProvider($this->dispatcher);
}
public function testGetObjectByNameIfItDoesNotExist(): void
{
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->isInstanceOf(InternalObjectEvent::class),
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS
);
$this->expectException(ObjectNotFoundException::class);
$this->objectProvider->getObjectByName('Unicorn');
}
public function testGetObjectByNameIfItExists(): void
{
$contact = new Contact();
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectEvent $e) use ($contact) {
// Fake a subscriber.
$e->addObject($contact);
return true;
}),
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS
);
$this->assertSame($contact, $this->objectProvider->getObjectByName(Contact::NAME));
}
public function testGetObjectByEntityNameIfItDoesNotExist(): void
{
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->isInstanceOf(InternalObjectEvent::class),
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS,
);
$this->expectException(ObjectNotFoundException::class);
$this->objectProvider->getObjectByEntityName('Unicorn');
}
public function testGetObjectByEntityNameIfItExists(): void
{
$contact = new Contact();
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectEvent $e) use ($contact) {
// Fake a subscriber.
$e->addObject($contact);
return true;
}),
IntegrationEvents::INTEGRATION_COLLECT_INTERNAL_OBJECTS
);
$this->assertSame($contact, $this->objectProvider->getObjectByEntityName(Lead::class));
}
}

View File

@@ -0,0 +1,145 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\ReportBuilder;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\Exception\FieldNotFoundException;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectHelper\ContactObjectHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FieldBuilder;
use PHPUnit\Framework\TestCase;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Routing\Router;
class FieldBuilderTest extends TestCase
{
/**
* @var Router|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $router;
/**
* @var FieldHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $fieldHelper;
/**
* @var ContactObjectHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $contactObjectHelper;
protected function setUp(): void
{
$this->router = $this->createMock(Router::class);
$this->fieldHelper = $this->getMockBuilder(FieldHelper::class)
->disableOriginalConstructor()
->onlyMethods(['getFieldList'])
->getMock();
$this->contactObjectHelper = $this->createMock(ContactObjectHelper::class);
}
public function testIdFieldIsAdded(): void
{
$field = $this->getFieldBuilder()->buildObjectField('mautic_internal_id', ['id' => 1], new ObjectDAO('Test'), 'Test');
$this->assertEquals('mautic_internal_id', $field->getName());
$this->assertEquals(FieldDAO::FIELD_CHANGED, $field->getState());
$this->assertEquals(1, $field->getValue()->getNormalizedValue());
}
public function testOwnerIdFieldIsAdded(): void
{
$field = $this->getFieldBuilder()->buildObjectField(
'owner_id',
['id' => 1, 'owner_id' => 123],
new ObjectDAO('Test'),
'Test'
);
$this->assertEquals('owner_id', $field->getName());
$this->assertEquals(FieldDAO::FIELD_CHANGED, $field->getState());
$this->assertEquals(123, $field->getValue()->getNormalizedValue());
}
public function testDoNotContactFieldIsAdded(): void
{
$this->contactObjectHelper->expects($this->once())
->method('getDoNotContactStatus')
->with(1, 'email')
->willReturn(0);
$field = $this->getFieldBuilder()->buildObjectField('mautic_internal_dnc_email', ['id' => 1], new ObjectDAO('Test'), 'Test');
$this->assertEquals('mautic_internal_dnc_email', $field->getName());
$this->assertEquals(FieldDAO::FIELD_CHANGED, $field->getState());
$this->assertEquals(0, $field->getValue()->getNormalizedValue());
}
public function testTimelineFieldIsAdded(): void
{
$this->router->expects($this->once())
->method('generate')
->with(
'mautic_plugin_timeline_view',
[
'integration' => 'Test',
'leadId' => 1,
],
UrlGeneratorInterface::ABSOLUTE_URL
);
$field = $this->getFieldBuilder()->buildObjectField('mautic_internal_contact_timeline', ['id' => 1], new ObjectDAO('Test'), 'Test');
$this->assertEquals('mautic_internal_contact_timeline', $field->getName());
$this->assertEquals(FieldDAO::FIELD_CHANGED, $field->getState());
$this->assertEquals('', $field->getValue()->getNormalizedValue());
}
public function testCustomFieldsAreAdded(): void
{
$this->fieldHelper->expects($this->once())
->method('getFieldList')
->with('Test')
->willReturn(
[
'email' => [
'type' => 'email',
],
]
);
$objectDAO = new ObjectDAO('Test');
$objectDAO->setRequiredFields(['email']);
$field = $this->getFieldBuilder()->buildObjectField('email', ['id' => 1, 'email' => 'test@test.com'], $objectDAO, 'Test');
$this->assertEquals('email', $field->getName());
$this->assertEquals(FieldDAO::FIELD_REQUIRED, $field->getState());
$this->assertEquals('test@test.com', $field->getValue()->getNormalizedValue());
}
public function testUnrecognizedFieldThrowsException(): void
{
$this->fieldHelper->expects($this->once())
->method('getFieldList')
->with('Test')
->willReturn(
[
'email' => [
'type' => 'email',
],
]
);
$this->expectException(FieldNotFoundException::class);
$this->getFieldBuilder()->buildObjectField('badfield', ['id' => 1, 'email' => 'test@test.com'], new ObjectDAO('Test'), 'Test');
}
public function getFieldBuilder()
{
return new FieldBuilder($this->router, $this->fieldHelper, $this->contactObjectHelper);
}
}

View File

@@ -0,0 +1,381 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\ReportBuilder;
use Mautic\IntegrationsBundle\Event\InternalCompanyEvent;
use Mautic\IntegrationsBundle\Event\InternalContactEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectFindByIdEvent;
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
use Mautic\IntegrationsBundle\Exception\InvalidValueException;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\RequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\FieldNotFoundException;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FieldBuilder;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FullObjectReportBuilder;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\LeadBundle\Entity\Company as CompanyEntity;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class FullObjectReportBuilderTest extends TestCase
{
private const INTEGRATION_NAME = 'Test';
private const TEST_EMAIL = 'test@test.com';
/**
* @var ObjectProvider|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $objectProvider;
/**
* @var EventDispatcherInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $dispatcher;
/**
* @var FieldBuilder|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $fieldBuilder;
private FullObjectReportBuilder $reportBuilder;
protected function setUp(): void
{
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->fieldBuilder = $this->createMock(FieldBuilder::class);
$this->reportBuilder = new FullObjectReportBuilder(
$this->fieldBuilder,
$this->objectProvider,
$this->dispatcher
);
}
public function testBuildingContactReport(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(Contact::NAME, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, $requestDAO->getSyncToIntegration())
->willReturn(
new FieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, self::TEST_EMAIL))
);
$internalObject = new Contact();
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectFindEvent $event) use ($internalObject, $fromDateTime, $toDateTime) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame($fromDateTime, $event->getDateRange()->getFromDate());
$this->assertSame($toDateTime, $event->getDateRange()->getToDate());
$this->assertSame(0, $event->getStart());
$this->assertSame(200, $event->getLimit());
// Mock a subscriber:
$event->setFoundObjects([
[
'id' => 1,
'email' => self::TEST_EMAIL,
'date_modified' => '2018-10-08 00:30:00',
],
]);
return true;
}),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(Contact::NAME);
$this->assertTrue(isset($objects[1]));
$this->assertEquals(self::TEST_EMAIL, $objects[1]->getField('email')->getValue()->getNormalizedValue());
}
public function testBuildingCompanyReport(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(MauticSyncDataExchange::OBJECT_COMPANY, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, $requestDAO->getSyncToIntegration())
->willReturn(
new FieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, self::TEST_EMAIL))
);
$internalObject = new Company();
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Company::NAME)
->willReturn($internalObject);
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectFindEvent $event) use ($internalObject, $fromDateTime, $toDateTime) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame($fromDateTime, $event->getDateRange()->getFromDate());
$this->assertSame($toDateTime, $event->getDateRange()->getToDate());
$this->assertSame(0, $event->getStart());
$this->assertSame(200, $event->getLimit());
// Mock a subscriber:
$event->setFoundObjects([
[
'id' => 1,
'email' => self::TEST_EMAIL,
'date_modified' => '2018-10-08 00:30:00',
],
]);
return true;
}),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(MauticSyncDataExchange::OBJECT_COMPANY);
$this->assertTrue(isset($objects[1]));
$this->assertEquals(self::TEST_EMAIL, $objects[1]->getField('email')->getValue()->getNormalizedValue());
}
/**
* @throws FieldNotFoundException
* @throws InvalidValueException
*/
public function testBuildingContactReportWithFindInternalRecordEvent(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(Contact::NAME, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, $requestDAO->getSyncToIntegration())
->willReturn(
new FieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, self::TEST_EMAIL))
);
$internalObject = new Contact();
$this->objectProvider->expects($this->exactly(2))
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
$matcher = $this->exactly(2);
$this->dispatcher->expects($matcher)
->method('hasListeners')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD, $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(IntegrationEvents::INTEGRATION_BEFORE_FULL_CONTACT_REPORT_BUILD, $parameters[0]);
}
return true;
});
$contactEntity = new class extends Lead {
public function getId(): int
{
return 1;
}
};
$matcher = $this->exactly(3);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher, $internalObject, $fromDateTime, $toDateTime, $contactEntity) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectFindEvent $event) use (
$internalObject,
$fromDateTime,
$toDateTime
) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame($fromDateTime, $event->getDateRange()->getFromDate());
$this->assertSame($toDateTime, $event->getDateRange()->getToDate());
$this->assertSame(0, $event->getStart());
$this->assertSame(200, $event->getLimit());
// Mock a subscriber:
$event->setFoundObjects(
[
[
'id' => 1,
'email' => self::TEST_EMAIL,
'date_modified' => '2018-10-08 00:30:00',
],
]
);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectFindByIdEvent $event) use ($internalObject, $contactEntity) {
$this->assertSame($internalObject, $event->getObject());
$event->setId($contactEntity->getId());
$event->setEntity($contactEntity);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD, $parameters[1]);
}
if (3 === $matcher->numberOfInvocations()) {
$callback = function (InternalContactEvent $event) use ($contactEntity) {
$this->assertSame($contactEntity, $event->getContact());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_BEFORE_FULL_CONTACT_REPORT_BUILD, $parameters[1]);
}
return $parameters[0];
});
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(Contact::NAME);
$this->assertTrue(isset($objects[1]));
$this->assertEquals(self::TEST_EMAIL, $objects[1]->getField('email')->getValue()->getNormalizedValue());
}
/**
* @throws FieldNotFoundException
* @throws InvalidValueException
*/
public function testBuildingCompanyReportWithFindInternalRecordEvent(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(MauticSyncDataExchange::OBJECT_COMPANY, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, $requestDAO->getSyncToIntegration())
->willReturn(
new FieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, self::TEST_EMAIL))
);
$internalObject = new Company();
$this->objectProvider->expects($this->exactly(2))
->method('getObjectByName')
->with(Company::NAME)
->willReturn($internalObject);
$matcher = $this->exactly(2);
$this->dispatcher->expects($matcher)
->method('hasListeners')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD, $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(IntegrationEvents::INTEGRATION_BEFORE_FULL_COMPANY_REPORT_BUILD, $parameters[0]);
}
return true;
});
$companyEntity = new class extends CompanyEntity {
public function getId(): int
{
return 1;
}
};
$matcher = $this->exactly(3);
$this->dispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher, $internalObject,
$fromDateTime,
$toDateTime,
$companyEntity) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectFindEvent $event) use (
$internalObject,
$fromDateTime,
$toDateTime
) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame($fromDateTime, $event->getDateRange()->getFromDate());
$this->assertSame($toDateTime, $event->getDateRange()->getToDate());
$this->assertSame(0, $event->getStart());
$this->assertSame(200, $event->getLimit());
// Mock a subscriber:
$event->setFoundObjects(
[
[
'id' => 1,
'email' => self::TEST_EMAIL,
'date_modified' => '2018-10-08 00:30:00',
],
]
);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (InternalObjectFindByIdEvent $event) use ($internalObject, $companyEntity) {
$this->assertSame($internalObject, $event->getObject());
$event->setId($companyEntity->getId());
$event->setEntity($companyEntity);
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORD, $parameters[1]);
}
if (3 === $matcher->numberOfInvocations()) {
$callback = function (InternalCompanyEvent $event) use ($companyEntity) {
$this->assertSame($companyEntity, $event->getCompany());
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_BEFORE_FULL_COMPANY_REPORT_BUILD, $parameters[1]);
}
return $parameters[0];
});
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(MauticSyncDataExchange::OBJECT_COMPANY);
$this->assertTrue(isset($objects[1]));
$this->assertEquals(self::TEST_EMAIL, $objects[1]->getField('email')->getValue()->getNormalizedValue());
}
}

View File

@@ -0,0 +1,400 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange\Internal\ReportBuilder;
use Mautic\IntegrationsBundle\Entity\FieldChangeRepository;
use Mautic\IntegrationsBundle\Event\InternalObjectFindEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\RequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\EncodedValueDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\FieldNotFoundException;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectNotFoundException;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company as InternalCompany;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ObjectProvider;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FieldBuilder;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\PartialObjectReportBuilder;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\LeadBundle\Entity\Company;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class PartialObjectReportBuilderTest extends TestCase
{
private const INTEGRATION_NAME = 'Test';
/**
* @var FieldChangeRepository|MockObject
*/
private MockObject $fieldChangeRepository;
/**
* @var FieldHelper|MockObject
*/
private MockObject $fieldHelper;
/**
* @var EventDispatcherInterface|MockObject
*/
private MockObject $dispatcher;
/**
* @var FieldBuilder|MockObject
*/
private MockObject $fieldBuilder;
/**
* @var ObjectProvider|MockObject
*/
private MockObject $objectProvider;
private PartialObjectReportBuilder $reportBuilder;
protected function setUp(): void
{
$this->fieldChangeRepository = $this->createMock(FieldChangeRepository::class);
$this->fieldHelper = $this->getMockBuilder(FieldHelper::class)
->disableOriginalConstructor()
->onlyMethods(['getFieldChangeObject', 'getFieldObjectName'])
->getMock();
$this->dispatcher = $this->createMock(EventDispatcherInterface::class);
$this->fieldBuilder = $this->createMock(FieldBuilder::class);
$this->objectProvider = $this->createMock(ObjectProvider::class);
$this->reportBuilder = new PartialObjectReportBuilder(
$this->fieldChangeRepository,
$this->fieldHelper,
$this->fieldBuilder,
$this->objectProvider,
$this->dispatcher
);
}
public function testTrackedContactChanges(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(Contact::NAME, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestObject->addField('firstname');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, self::INTEGRATION_NAME)
->willReturn(
new FieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com'))
);
$fieldChange = [
'object_type' => Lead::class,
'object_id' => 1,
'modified_at' => '2018-10-08 00:30:00',
'column_name' => 'firstname',
'column_type' => EncodedValueDAO::STRING_TYPE,
'column_value' => 'Bob',
];
$this->fieldHelper->expects($this->once())
->method('getFieldChangeObject')
->with($fieldChange)
->willReturn(
new FieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob'))
);
$this->fieldHelper->expects($this->once())
->method('getFieldObjectName')
->with(Contact::NAME)
->willReturn(Lead::class);
// Find and return tracked changes
$this->fieldChangeRepository->expects($this->once())
->method('findChangesBefore')
->with(
'Test',
Lead::class,
$toDateTime,
0
)
->willReturn([$fieldChange]);
$internalObject = new Contact();
$this->objectProvider->expects($this->once())
->method('getObjectByEntityName')
->with(Lead::class)
->willReturn($internalObject);
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(Contact::NAME)
->willReturn($internalObject);
// Find the complete object
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectFindEvent $event) use ($internalObject) {
$this->assertSame($internalObject, $event->getObject());
$this->assertSame([1], $event->getIds());
// Mock a subscriber:
$event->setFoundObjects([
[
'id' => 1,
'email' => 'test@test.com',
'firstname' => 'Bob and Cat',
],
]);
return true;
}),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(Contact::NAME);
$this->assertTrue(isset($objects[1]));
$this->assertEquals('test@test.com', $objects[1]->getField('email')->getValue()->getNormalizedValue());
$this->assertEquals('Bob', $objects[1]->getField('firstname')->getValue()->getNormalizedValue());
}
public function testTrackedCompanyChanges(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(MauticSyncDataExchange::OBJECT_COMPANY, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestObject->addField('companyname');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, self::INTEGRATION_NAME)
->willReturn(
new FieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com'))
);
$fieldChange = [
'object_type' => Company::class,
'object_id' => 1,
'modified_at' => '2018-10-08 00:30:00',
'column_name' => 'firstname',
'column_type' => EncodedValueDAO::STRING_TYPE,
'column_value' => 'Bob',
];
$this->fieldHelper->expects($this->once())
->method('getFieldChangeObject')
->with($fieldChange)
->willReturn(
new FieldDAO('companyname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob and Cat'))
);
$this->fieldHelper->expects($this->once())
->method('getFieldObjectName')
->with(InternalCompany::NAME)
->willReturn(Company::class);
// Find and return tracked changes
$this->fieldChangeRepository->expects($this->once())
->method('findChangesBefore')
->with(
'Test',
Company::class,
$toDateTime,
0
)
->willReturn([$fieldChange]);
$internalObject = new InternalCompany();
$this->objectProvider->expects($this->once())
->method('getObjectByEntityName')
->with(Company::class)
->willReturn($internalObject);
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(InternalCompany::NAME)
->willReturn($internalObject);
// Find the complete object
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectFindEvent $event) use ($internalObject) {
$this->assertSame([1], $event->getIds());
$this->assertSame($internalObject, $event->getObject());
// Mock a subscriber:
$event->setFoundObjects([
[
'id' => 1,
'email' => 'test@test.com',
'companyname' => 'Bob and Cat',
],
]);
return true;
}),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(InternalCompany::NAME);
$this->assertTrue(isset($objects[1]));
$this->assertEquals('test@test.com', $objects[1]->getField('email')->getValue()->getNormalizedValue());
$this->assertEquals('Bob and Cat', $objects[1]->getField('companyname')->getValue()->getNormalizedValue());
}
public function testTrackedContactChangesThrowsObjectNotFoundException(): void
{
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(Contact::NAME, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestObject->addField('firstname');
$requestDAO->addObject($requestObject);
$fieldChange = [
'object_type' => Lead::class,
'object_id' => 1,
'modified_at' => '2018-10-08 00:30:00',
'column_name' => 'firstname',
'column_type' => EncodedValueDAO::STRING_TYPE,
'column_value' => 'Bob',
];
$this->fieldHelper->expects($this->once())
->method('getFieldObjectName')
->with(Contact::NAME)
->willReturn(Lead::class);
// Find and return tracked changes
$this->fieldChangeRepository->expects($this->once())
->method('findChangesBefore')
->with(
'Test',
Lead::class,
$toDateTime,
0
)
->willReturn([$fieldChange]);
$this->objectProvider->expects($this->once())
->method('getObjectByEntityName')
->with(Lead::class)
->willThrowException(new ObjectNotFoundException(Contact::NAME));
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(Contact::NAME);
$this->assertEmpty($objects);
}
public function testTrackedContactChangesFieldNotFoundException(): void
{
$exception = new FieldNotFoundException('email', 'company');
$this->expectExceptionObject($exception);
$requestDAO = new RequestDAO(self::INTEGRATION_NAME, 1, new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]));
$fromDateTime = new \DateTimeImmutable('2018-10-08 00:00:00');
$toDateTime = new \DateTimeImmutable('2018-10-08 00:01:00');
$requestObject = new ObjectDAO(MauticSyncDataExchange::OBJECT_COMPANY, $fromDateTime, $toDateTime);
$requestObject->addField('email');
$requestObject->addField('companyname');
$requestDAO->addObject($requestObject);
$this->fieldBuilder->expects($this->once())
->method('buildObjectField')
->with('email', $this->anything(), $requestObject, self::INTEGRATION_NAME)
->willThrowException(new FieldNotFoundException('email', $requestObject->getObject()));
$fieldChange = [
'object_type' => Company::class,
'object_id' => 1,
'modified_at' => '2018-10-08 00:30:00',
'column_name' => 'firstname',
'column_type' => EncodedValueDAO::STRING_TYPE,
'column_value' => 'Bob',
];
$this->fieldHelper->expects($this->once())
->method('getFieldChangeObject')
->with($fieldChange)
->willReturn(
new FieldDAO('companyname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob and Cat'))
);
$this->fieldHelper->expects($this->once())
->method('getFieldObjectName')
->with(InternalCompany::NAME)
->willReturn(Company::class);
// Find and return tracked changes
$this->fieldChangeRepository->expects($this->once())
->method('findChangesBefore')
->with(
'Test',
Company::class,
$toDateTime,
0
)
->willReturn([$fieldChange]);
$internalObject = new InternalCompany();
$this->objectProvider->expects($this->once())
->method('getObjectByEntityName')
->with(Company::class)
->willReturn($internalObject);
$this->objectProvider->expects($this->once())
->method('getObjectByName')
->with(InternalCompany::NAME)
->willReturn($internalObject);
// Find the complete object
$this->dispatcher->expects($this->once())
->method('dispatch')
->with(
$this->callback(function (InternalObjectFindEvent $event) use ($internalObject) {
$this->assertSame([1], $event->getIds());
$this->assertSame($internalObject, $event->getObject());
// Mock a subscriber:
$event->setFoundObjects([
[
'id' => 1,
'email' => 'test@test.com',
'companyname' => 'Bob and Cat',
],
]);
return true;
}),
IntegrationEvents::INTEGRATION_FIND_INTERNAL_RECORDS
);
$report = $this->reportBuilder->buildReport($requestDAO);
$objects = $report->getObjects(InternalCompany::NAME);
$this->assertTrue(isset($objects[1]));
// trying to access non-existent object
$objects[1]->getField('email');
}
}

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncDataExchange;
use Mautic\IntegrationsBundle\Entity\FieldChangeRepository;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\RequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Helper\MappingHelper;
use Mautic\IntegrationsBundle\Sync\Helper\SyncDateHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Executioner\OrderExecutioner;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\FullObjectReportBuilder;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\ReportBuilder\PartialObjectReportBuilder;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\LeadBundle\Entity\Lead;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class MauticSyncDataExchangeTest extends TestCase
{
/**
* @var MockObject|FieldChangeRepository
*/
private MockObject $fieldChangeRepository;
/**
* @var MockObject|FieldHelper
*/
private MockObject $fieldHelper;
/**
* @var MockObject|MappingHelper
*/
private MockObject $mappingHelper;
/**
* @var MockObject|FullObjectReportBuilder
*/
private MockObject $fullObjectReportBuilder;
/**
* @var MockObject|PartialObjectReportBuilder
*/
private MockObject $partialObjectReportBuilder;
/**
* @var MockObject|OrderExecutioner
*/
private MockObject $orderExecutioner;
private MauticSyncDataExchange $mauticSyncDataExchange;
/**
* @var SyncDateHelper&MockObject
*/
private MockObject $syncDateHelper;
protected function setUp(): void
{
$this->fieldChangeRepository = $this->createMock(FieldChangeRepository::class);
$this->fieldHelper = $this->createMock(FieldHelper::class);
$this->mappingHelper = $this->createMock(MappingHelper::class);
$this->fullObjectReportBuilder = $this->createMock(FullObjectReportBuilder::class);
$this->partialObjectReportBuilder = $this->createMock(PartialObjectReportBuilder::class);
$this->orderExecutioner = $this->createMock(OrderExecutioner::class);
$this->syncDateHelper = $this->createMock(SyncDateHelper::class);
$this->mauticSyncDataExchange = new MauticSyncDataExchange(
$this->fieldChangeRepository,
$this->fieldHelper,
$this->mappingHelper,
$this->fullObjectReportBuilder,
$this->partialObjectReportBuilder,
$this->orderExecutioner,
$this->syncDateHelper
);
}
public function testFirstTimeSyncUsesFullObjectBuilder(): void
{
$inputOptionsDAO = new InputOptionsDAO(
[
'integration' => 'foobar',
'first-time-sync' => true,
]
);
$requestDAO = new RequestDAO('foobar', 1, $inputOptionsDAO);
$this->fullObjectReportBuilder->expects($this->once())
->method('buildReport')
->with($requestDAO);
$this->partialObjectReportBuilder->expects($this->never())
->method('buildReport')
->with($requestDAO);
$this->mauticSyncDataExchange->getSyncReport($requestDAO);
}
public function testSyncingSpecificMauticIdsUseFullObjectBuilder(): void
{
$inputOptionsDAO = new InputOptionsDAO(
[
'integration' => 'foobar',
'mautic-object-id' => [1, 2, 3],
]
);
$requestDAO = new RequestDAO('foobar', 1, $inputOptionsDAO);
$this->fullObjectReportBuilder->expects($this->once())
->method('buildReport')
->with($requestDAO);
$this->partialObjectReportBuilder->expects($this->never())
->method('buildReport')
->with($requestDAO);
$this->mauticSyncDataExchange->getSyncReport($requestDAO);
}
public function testUseOfPartialObjectBuilder(): void
{
$inputOptionsDAO = new InputOptionsDAO(
[
'integration' => 'foobar',
]
);
$requestDAO = new RequestDAO('foobar', 1, $inputOptionsDAO);
$this->fullObjectReportBuilder->expects($this->never())
->method('buildReport')
->with($requestDAO);
$this->partialObjectReportBuilder->expects($this->once())
->method('buildReport')
->with($requestDAO);
$this->mauticSyncDataExchange->getSyncReport($requestDAO);
}
public function testGetConflictedInternalObjectWithNoObjectId(): void
{
$mappingManualDao = new MappingManualDAO('IntegrationA');
$integrationObjectDao = new ObjectDAO('Lead', 'some-SF-ID');
$this->mappingHelper->expects($this->once())
->method('findMauticObject')
->with($mappingManualDao, 'lead', $integrationObjectDao)
->willReturn(new ObjectDAO('lead', null));
// No need to make the DB query when ID is null.
$this->fieldChangeRepository->expects($this->never())
->method('findChangesForObject');
$internalObjectDao = $this->mauticSyncDataExchange->getConflictedInternalObject($mappingManualDao, 'lead', $integrationObjectDao);
Assert::assertSame('lead', $internalObjectDao->getObject());
Assert::assertNull($internalObjectDao->getObjectId());
}
public function testGetConflictedInternalObjectWithObjectId(): void
{
$mappingManualDao = new MappingManualDAO('IntegrationA');
$integrationObjectDao = new ObjectDAO('Lead', 'some-SF-ID');
$fieldChange = [
'modified_at' => '2020-08-25 17:20:00',
'column_type' => 'text',
'column_value' => 'some-field-value',
'column_name' => 'some-field-name',
];
$this->mappingHelper->expects($this->once())
->method('findMauticObject')
->with($mappingManualDao, 'lead', $integrationObjectDao)
->willReturn(new ObjectDAO('lead', 123));
$this->mappingHelper->method('getMauticEntityClassName')
->with('lead')
->willReturn(Lead::class);
$this->fieldHelper->method('getFieldChangeObject')
->with($fieldChange)
->willReturn(new FieldDAO('some-field-name', new NormalizedValueDAO('type', 'some-field-value')));
$this->fieldChangeRepository->expects($this->once())
->method('findChangesForObject')
->with('IntegrationA', Lead::class, 123)
->willReturn([$fieldChange]);
$internalObjectDao = $this->mauticSyncDataExchange->getConflictedInternalObject($mappingManualDao, 'lead', $integrationObjectDao);
Assert::assertSame('lead', $internalObjectDao->getObject());
Assert::assertSame(123, $internalObjectDao->getObjectId());
Assert::assertCount(1, $internalObjectDao->getFields());
}
}

View File

@@ -0,0 +1,184 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncJudge\Modes;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InformationChangeRequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ConflictUnresolvedException;
use Mautic\IntegrationsBundle\Sync\SyncJudge\Modes\BestEvidence;
use PHPUnit\Framework\TestCase;
class BestEvidenceTest extends TestCase
{
public function testLeftWinnerWithCertainChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$winner = BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($leftChangeRequest, $winner);
}
public function testRightWinnerWithCertainChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$winner = BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($rightChangeRequest, $winner);
}
public function testLeftWinnerWithPossibleChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$winner = BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($leftChangeRequest, $winner);
}
public function testRightWinnerWithPossibleChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$winner = BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($rightChangeRequest, $winner);
}
public function testUnresolvedConflictExceptionThrownIfEqual(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$this->expectException(ConflictUnresolvedException::class);
BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownWhenLeftPossibleChangeDateTimeIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$this->expectException(ConflictUnresolvedException::class);
BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownWhenRightPossibleChangeDateTimeIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$this->expectException(ConflictUnresolvedException::class);
BestEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
}

View File

@@ -0,0 +1,304 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncJudge\Modes;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InformationChangeRequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ConflictUnresolvedException;
use Mautic\IntegrationsBundle\Sync\SyncJudge\Modes\FuzzyEvidence;
use PHPUnit\Framework\TestCase;
class FuzzyEvidenceTest extends TestCase
{
public function testLeftWinnerWithCertainChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$winner = FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($leftChangeRequest, $winner);
}
public function testRightWinnerWithCertainChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$winner = FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($rightChangeRequest, $winner);
}
public function testLeftWinnerWithPossibleChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$winner = FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($leftChangeRequest, $winner);
}
public function testRightWinnerWithPossibleChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$winner = FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($rightChangeRequest, $winner);
}
public function testLeftWinnerWithCertainChangeDateTimeNewerThanRightPossibleChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$winner = FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($leftChangeRequest, $winner);
}
public function testRightWinnerWithCertainChangeDateTimeNewerThanLeftPossibleChangeDateTime(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$winner = FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($rightChangeRequest, $winner);
}
public function testUnresolvedConflictExceptionThrownIfLeftCertainIsEqualToRightPossible(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$this->expectException(ConflictUnresolvedException::class);
FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownIfRightCertainIsEqualToLeftPossible(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$this->expectException(ConflictUnresolvedException::class);
FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownIfLeftCertainIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$this->expectException(ConflictUnresolvedException::class);
FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownIfRightCertainIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$this->expectException(ConflictUnresolvedException::class);
FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownIfLeftPossibleIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$this->expectException(ConflictUnresolvedException::class);
FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownIfRightPossibleIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setPossibleChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$this->expectException(ConflictUnresolvedException::class);
FuzzyEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
}

View File

@@ -0,0 +1,134 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncJudge\Modes;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InformationChangeRequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ConflictUnresolvedException;
use Mautic\IntegrationsBundle\Sync\SyncJudge\Modes\HardEvidence;
use PHPUnit\Framework\TestCase;
class HardEvidenceTest extends TestCase
{
public function testLeftWinner(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$winner = HardEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($leftChangeRequest, $winner);
}
public function testRightWinner(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:01:00'));
$winner = HardEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
$this->assertEquals($rightChangeRequest, $winner);
}
public function testUnresolvedConflictExceptionThrownIfEqual(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$this->expectException(ConflictUnresolvedException::class);
HardEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownWhenLeftCertainChangeDateTimeIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$rightChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$this->expectException(ConflictUnresolvedException::class);
HardEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
public function testUnresolvedConflictExceptionThrownWhenRightCertainChangeDateTimeIsNull(): void
{
$leftChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$leftChangeRequest->setCertainChangeDateTime(new \DateTimeImmutable('2018-10-08 00:00:00'));
$rightChangeRequest = new InformationChangeRequestDAO(
'Test',
'Object',
1,
'field',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'test')
);
$this->expectException(ConflictUnresolvedException::class);
HardEvidence::adjudicate($leftChangeRequest, $rightChangeRequest);
}
}

View File

@@ -0,0 +1,113 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncProcess\Direction\Helper;
use Mautic\IntegrationsBundle\Exception\RequiredValueException;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Helper\ValueHelper;
use PHPUnit\Framework\TestCase;
class ValueHelperTest extends TestCase
{
public function testExceptionForMissingRequiredIntegrationValue(): void
{
$this->expectException(RequiredValueException::class);
$normalizedValueDAO = new NormalizedValueDAO(NormalizedValueDAO::STRING_TYPE, '');
$this->getValueHelper()->getValueForIntegration(
$normalizedValueDAO,
FieldDAO::FIELD_REQUIRED,
ObjectMappingDAO::SYNC_TO_INTEGRATION
);
}
public function testNoExceptionForMissingNonRequiredIntegrationValue(): void
{
$normalizedValueDAO = new NormalizedValueDAO(NormalizedValueDAO::STRING_TYPE, '');
$newValue = $this->getValueHelper()->getValueForIntegration(
$normalizedValueDAO,
FieldDAO::FIELD_CHANGED,
ObjectMappingDAO::SYNC_TO_MAUTIC
);
$this->assertEquals(
'',
$newValue->getNormalizedValue()
);
}
public function testNoExceptionForMissingOppositeSyncIntegrationValue(): void
{
$normalizedValueDAO = new NormalizedValueDAO(NormalizedValueDAO::STRING_TYPE, '');
$newValue = $this->getValueHelper()->getValueForIntegration(
$normalizedValueDAO,
FieldDAO::FIELD_CHANGED,
ObjectMappingDAO::SYNC_TO_INTEGRATION
);
$this->assertEquals(
'',
$newValue->getNormalizedValue()
);
}
public function testExceptionForMissingRequiredMauticValue(): void
{
$this->expectException(RequiredValueException::class);
$normalizedValueDAO = new NormalizedValueDAO(NormalizedValueDAO::STRING_TYPE, '');
$this->getValueHelper()->getValueForMautic(
$normalizedValueDAO,
FieldDAO::FIELD_REQUIRED,
ObjectMappingDAO::SYNC_TO_MAUTIC
);
}
public function testNoExceptionForMissingNonRequiredInternalValue(): void
{
$normalizedValueDAO = new NormalizedValueDAO(NormalizedValueDAO::STRING_TYPE, '');
$newValue = $this->getValueHelper()->getValueForMautic(
$normalizedValueDAO,
FieldDAO::FIELD_CHANGED,
ObjectMappingDAO::SYNC_TO_INTEGRATION
);
$this->assertEquals(
'',
$newValue->getNormalizedValue()
);
}
public function testNoExceptionForMissingOppositeSyncInternalnValue(): void
{
$normalizedValueDAO = new NormalizedValueDAO(NormalizedValueDAO::STRING_TYPE, '');
$newValue = $this->getValueHelper()->getValueForMautic(
$normalizedValueDAO,
FieldDAO::FIELD_CHANGED,
ObjectMappingDAO::SYNC_TO_MAUTIC
);
$this->assertEquals(
'',
$newValue->getNormalizedValue()
);
}
/**
* @return ValueHelper
*/
private function getValueHelper()
{
return new ValueHelper();
}
}

View File

@@ -0,0 +1,241 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncProcess\Direction\Integration;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO as OrderFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO as ReportFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO as ReportObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ReportDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\ObjectDAO as RequestObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\RequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Helper\MappingHelper;
use Mautic\IntegrationsBundle\Sync\Helper\SyncDateHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\SyncDataExchangeInterface;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Integration\IntegrationSyncProcess;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Integration\ObjectChangeGenerator;
use PHPUnit\Framework\TestCase;
class IntegrationSyncProcessTest extends TestCase
{
private const INTEGRATION_NAME = 'Test';
/**
* @var SyncDateHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $syncDateHelper;
/**
* @var MappingHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $mappingHelper;
/**
* @var ObjectChangeGenerator|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $objectChangeGenerator;
/**
* @var SyncDataExchangeInterface|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $syncDataExchange;
/**
* @var InputOptionsDAO
*/
private $inputOptionsDAO;
/**
* @var IntegrationSyncProcess
*/
private $integrationSyncProcess;
protected function setUp(): void
{
$this->syncDateHelper = $this->createMock(SyncDateHelper::class);
$this->mappingHelper = $this->createMock(MappingHelper::class);
$this->objectChangeGenerator = $this->createMock(ObjectChangeGenerator::class);
$this->syncDataExchange = $this->createMock(SyncDataExchangeInterface::class);
$this->inputOptionsDAO = new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]);
$this->integrationSyncProcess = new IntegrationSyncProcess($this->syncDateHelper, $this->mappingHelper, $this->objectChangeGenerator);
}
public function testThatIntegrationGetSyncReportIsCalledBasedOnRequest(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
$fromSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncFromDateTime')
->with(self::INTEGRATION_NAME, $objectName)
->willReturn($fromSyncDateTime);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncToDateTime')
->willReturn($toSyncDateTime);
// SyncDateExchangeInterface::getSyncReport should sync because an object was added to the report
$this->syncDataExchange->expects($this->once())
->method('getSyncReport')
->willReturnCallback(
function (RequestDAO $requestDAO) use ($objectName) {
$requestObjects = $requestDAO->getObjects();
$this->assertCount(1, $requestObjects);
/** @var RequestObjectDAO $requestObject */
$requestObject = $requestObjects[0];
$this->assertEquals(['email'], $requestObject->getRequiredFields());
$this->assertEquals(['email', 'first_name'], $requestObject->getFields());
$this->assertEquals($objectName, $requestObject->getObject());
return new ReportDAO(self::INTEGRATION_NAME);
}
);
$this->getSyncProcess($mappingManual)->getSyncReport(1);
}
public function testThatIntegrationGetSyncReportIsNotCalledBasedOnRequest(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$this->syncDateHelper->expects($this->never())
->method('getSyncFromDateTime')
->with(self::INTEGRATION_NAME, $objectName);
// SyncDateExchangeInterface::getSyncReport should sync because an object was added to the report
$this->syncDataExchange->expects($this->never())
->method('getSyncReport');
$report = $this->getSyncProcess($mappingManual)->getSyncReport(1);
$this->assertEquals(self::INTEGRATION_NAME, $report->getIntegration());
}
public function testOrderIsBuiltBasedOnMapping(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncDateTime')
->willReturn($toSyncDateTime);
$syncReport = new ReportDAO(MauticSyncDataExchange::NAME);
$objectDAO = new ReportObjectDAO(Contact::NAME, 1);
$objectDAO->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$objectDAO->addField(new ReportFieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$syncReport->addObject($objectDAO);
// It should search for an integration object mapped to an internal object
$this->mappingHelper->expects($this->once())
->method('findIntegrationObject')
->with(self::INTEGRATION_NAME, $objectName, $objectDAO)
->willReturn(
new ReportObjectDAO($objectName, 2)
);
$objectChangeDAO = new ObjectChangeDAO(self::INTEGRATION_NAME, $objectName, 2, Contact::NAME, 1);
$objectChangeDAO->addField(new OrderFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$objectChangeDAO->addField(new OrderFieldDAO('first_name', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$this->objectChangeGenerator->expects($this->once())
->method('getSyncObjectChange')
->willReturn($objectChangeDAO);
$syncOrder = $this->getSyncProcess($mappingManual)->getSyncOrder($syncReport);
// The change should have been added to the order as an identified object
$this->assertEquals([$objectName => [2 => $objectChangeDAO]], $syncOrder->getIdentifiedObjects());
}
private function getSyncProcess(MappingManualDAO $mappingManualDAO): IntegrationSyncProcess
{
$this->integrationSyncProcess->setupSync($this->inputOptionsDAO, $mappingManualDAO, $this->syncDataExchange);
return $this->integrationSyncProcess;
}
public function testThatItDoesntSyncOtherEntityTypesWhenIDsForSomeEntityAreSpecified(): void
{
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$this->inputOptionsDAO = new InputOptionsDAO([
'integration' => self::INTEGRATION_NAME,
'mautic-object-id' => ['contact:1'],
]);
$contactMapping = new ObjectMappingDAO(Contact::NAME, 'Contact');
$contactMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$mappingManual->addObjectMapping($contactMapping);
$leadMapping = new ObjectMappingDAO(Contact::NAME, 'Lead');
$leadMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$mappingManual->addObjectMapping($leadMapping);
$companyMapping = new ObjectMappingDAO(Company::NAME, 'Account');
$companyMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$mappingManual->addObjectMapping($companyMapping);
$fromSyncDateTime = new \DateTimeImmutable();
$matcher = $this->exactly(2);
$this->syncDateHelper->expects($matcher)
->method('getSyncFromDateTime')->willReturnCallback(function (...$parameters) use ($matcher, $fromSyncDateTime) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(self::INTEGRATION_NAME, $parameters[0]);
$this->assertSame('Contact', $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(self::INTEGRATION_NAME, $parameters[0]);
$this->assertSame('Lead', $parameters[1]);
}
return $fromSyncDateTime;
});
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->exactly(2))
->method('getSyncToDateTime')
->willReturn($toSyncDateTime);
// SyncDateExchangeInterface::getSyncReport should sync because an object was added to the report
$this->syncDataExchange->expects($this->once())
->method('getSyncReport')
->willReturnCallback(
function (RequestDAO $requestDAO): ReportDAO {
$requestObjects = $requestDAO->getObjects();
$this->assertCount(2, $requestObjects);
/** @var RequestObjectDAO $requestObject */
$requestObject = $requestObjects[0];
$this->assertEquals(['email'], $requestObject->getRequiredFields());
$this->assertEquals(['email'], $requestObject->getFields());
$this->assertEquals('Contact', $requestObject->getObject());
return new ReportDAO(self::INTEGRATION_NAME);
}
);
$syncReport = $this->getSyncProcess($mappingManual)->getSyncReport(1);
$this->assertEquals(self::INTEGRATION_NAME, $syncReport->getIntegration());
}
}

View File

@@ -0,0 +1,206 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncProcess\Direction\Integration;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO as ReportFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO as ReportObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ReportDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Helper\ValueHelper;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Integration\ObjectChangeGenerator;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\TestCase;
class ObjectChangeGeneratorTest extends TestCase
{
/**
* @var ValueHelper|\PHPUnit\Framework\MockObject\MockObject
*/
private \PHPUnit\Framework\MockObject\MockObject $valueHelper;
protected function setUp(): void
{
$this->valueHelper = $this->createMock(ValueHelper::class);
}
public function testFieldIsAddedToObjectChange(): void
{
$this->valueHelper->method('getValueForIntegration')
->willReturnCallback(
fn (NormalizedValueDAO $normalizedValueDAO, string $fieldState, string $syncDirection) => $normalizedValueDAO
);
$integration = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integration, $objectName);
$syncReport = $this->getInternalSyncReport();
$integrationReportObject = new ReportObjectDAO($objectName, 2);
$integrationReportObject->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$integrationReportObject->addField(new ReportFieldDAO('first_name', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$objectChangeGenerator = $this->getObjectChangeGenerator();
$objectChangeDAO = $objectChangeGenerator->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$syncReport->getObject(Contact::NAME, 1),
$integrationReportObject
);
$this->assertEquals($integration, $objectChangeDAO->getIntegration());
// object and object ID should be the integrations (from the integration's POV)
$this->assertEquals($objectName, $objectChangeDAO->getObject());
$this->assertEquals(2, $objectChangeDAO->getObjectId());
// mapped object and ID should be Mautic's
$this->assertEquals(Contact::NAME, $objectChangeDAO->getMappedObject());
$this->assertEquals(1, $objectChangeDAO->getMappedObjectId());
// Email should be a required field
$requiredFields = $objectChangeDAO->getRequiredFields();
$this->assertTrue(isset($requiredFields['email']));
// Both fields should be included
$fields = $objectChangeDAO->getFields();
$this->assertTrue(isset($fields['email']) && isset($fields['first_name']));
// First name is presumed to be changed
$changedFields = $objectChangeDAO->getChangedFields();
$this->assertTrue(isset($changedFields['first_name']));
}
public function testFieldIsNotAddedToObjectChangeIfNotFound(): void
{
$this->valueHelper->method('getValueForIntegration')
->willReturnCallback(
fn (NormalizedValueDAO $normalizedValueDAO, string $fieldState, string $syncDirection) => $normalizedValueDAO
);
$integration = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integration, $objectName);
$syncReport = $this->getInternalSyncReport(false);
$integrationReportObject = new ReportObjectDAO($objectName, 2);
$integrationReportObject->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$integrationReportObject->addField(new ReportFieldDAO('first_name', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$objectChangeGenerator = $this->getObjectChangeGenerator();
$objectChangeDAO = $objectChangeGenerator->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$syncReport->getObject(Contact::NAME, 1),
$integrationReportObject
);
$this->assertEquals($integration, $objectChangeDAO->getIntegration());
// object and object ID should be the integrations (from the integration's POV)
$this->assertEquals($objectName, $objectChangeDAO->getObject());
$this->assertEquals(2, $objectChangeDAO->getObjectId());
// mapped object and ID should be Mautic's
$this->assertEquals(Contact::NAME, $objectChangeDAO->getMappedObject());
$this->assertEquals(1, $objectChangeDAO->getMappedObjectId());
// Email should be a required field
$requiredFields = $objectChangeDAO->getRequiredFields();
$this->assertTrue(isset($requiredFields['email']));
// First name should not be included because it wasn't found in the internal object
$fields = $objectChangeDAO->getFields();
$this->assertFalse(isset($fields['first_name']));
}
public function testFieldsWithDirectionToIntegrationAreSkipped(): void
{
$objectChangeGenerator = new ObjectChangeGenerator(
new class extends ValueHelper {
}
);
$integrationName = 'Integration A';
$reportDAO = new ReportDAO($integrationName);
$mappingManualDAO = new MappingManualDAO($integrationName);
$objectMappingDAO = new ObjectMappingDAO(Contact::NAME, 'Lead');
$internalObject = new ReportObjectDAO(Contact::NAME, 123);
$integrationObject = new ReportObjectDAO('Lead', 'integration-id-1');
$objectMappingDAO->addFieldMapping('email', 'Email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMappingDAO->addFieldMapping('firstname', 'FirstName', ObjectMappingDAO::SYNC_TO_INTEGRATION);
$objectMappingDAO->addFieldMapping('points', 'Score', ObjectMappingDAO::SYNC_TO_MAUTIC);
$internalObject->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'john@doe.email')));
$internalObject->addField(new ReportFieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'John')));
$internalObject->addField(new ReportFieldDAO('points', new NormalizedValueDAO(NormalizedValueDAO::INT_TYPE, 40)));
$reportDAO->addObject($internalObject);
$objectChange = $objectChangeGenerator->getSyncObjectChange($reportDAO, $mappingManualDAO, $objectMappingDAO, $internalObject, $integrationObject);
// The points/Score field should not be recorded as a change because it has direction to Mautic.
Assert::assertCount(2, $objectChange->getFields());
Assert::assertSame('john@doe.email', $objectChange->getField('Email')->getValue()->getNormalizedValue());
Assert::assertSame('John', $objectChange->getField('FirstName')->getValue()->getNormalizedValue());
Assert::assertSame(Contact::NAME, $objectChange->getMappedObject());
Assert::assertSame(123, $objectChange->getMappedObjectId());
Assert::assertSame('integration-id-1', $objectChange->getObjectId());
Assert::assertSame('Lead', $objectChange->getObject());
Assert::assertSame($integrationName, $objectChange->getIntegration());
}
/**
* @return MappingManualDAO
*/
private function getMappingManual(string $integration, string $objectName)
{
$mappingManual = new MappingManualDAO($integration);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
return $mappingManual;
}
/**
* @param bool $includeFirstNameField
*
* @return ReportDAO
*/
private function getInternalSyncReport($includeFirstNameField = true)
{
$syncReport = new ReportDAO(MauticSyncDataExchange::NAME);
$internalReportObject = new ReportObjectDAO(Contact::NAME, 1);
$internalReportObject->addField(
new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com'), ReportFieldDAO::FIELD_REQUIRED)
);
if ($includeFirstNameField) {
$internalReportObject->addField(new ReportFieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
}
$syncReport->addObject($internalReportObject);
return $syncReport;
}
/**
* @return ObjectChangeGenerator
*/
private function getObjectChangeGenerator()
{
return new ObjectChangeGenerator($this->valueHelper);
}
}

View File

@@ -0,0 +1,298 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncProcess\Direction\Internal;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\FieldDAO as OrderFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO as ReportFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO as ReportObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ReportDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\ObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\ObjectDAO as RequestObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Request\RequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectDeletedException;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectSyncSkippedException;
use Mautic\IntegrationsBundle\Sync\Helper\SyncDateHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Company;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\MauticSyncProcess;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class MauticSyncProcessTest extends TestCase
{
private const INTEGRATION_NAME = 'Test';
/**
* @var SyncDateHelper|MockObject
*/
private MockObject $syncDateHelper;
/**
* @var ObjectChangeGenerator|MockObject
*/
private MockObject $objectChangeGenerator;
/**
* @var MauticSyncDataExchange|MockObject
*/
private MockObject $syncDataExchange;
/**
* @var InputOptionsDAO
*/
private $inputOptionsDAO;
protected function setUp(): void
{
$this->syncDateHelper = $this->createMock(SyncDateHelper::class);
$this->objectChangeGenerator = $this->createMock(ObjectChangeGenerator::class);
$this->syncDataExchange = $this->createMock(MauticSyncDataExchange::class);
$this->inputOptionsDAO = new InputOptionsDAO(['integration' => self::INTEGRATION_NAME]);
}
public function testThatMauticGetSyncReportIsCalledBasedOnRequest(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
$fromSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncFromDateTime')
->with(MauticSyncDataExchange::NAME, Contact::NAME)
->willReturn($fromSyncDateTime);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncToDateTime')
->willReturn($toSyncDateTime);
// SyncDateExchangeInterface::getSyncReport should sync because an object was added to the report
$this->syncDataExchange->expects($this->once())
->method('getSyncReport')
->willReturnCallback(
function (RequestDAO $requestDAO) {
$requestObjects = $requestDAO->getObjects();
$this->assertCount(1, $requestObjects);
/** @var RequestObjectDAO $requestObject */
$requestObject = $requestObjects[0];
$this->assertEquals(['email'], $requestObject->getRequiredFields());
$this->assertEquals(['email', 'firstname'], $requestObject->getFields());
$this->assertEquals(Contact::NAME, $requestObject->getObject());
return new ReportDAO(self::INTEGRATION_NAME);
}
);
$this->createMauticSyncProcess($mappingManual)->getSyncReport(1);
}
public function testThatMauticGetSyncReportIsNotCalledBasedOnRequest(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$this->syncDateHelper->expects($this->never())
->method('getSyncFromDateTime')
->with(self::INTEGRATION_NAME, $objectName);
// SyncDateExchangeInterface::getSyncReport should sync because an object was added to the report
$this->syncDataExchange->expects($this->never())
->method('getSyncReport');
$report = $this->createMauticSyncProcess($mappingManual)->getSyncReport(1);
$this->assertEquals(MauticSyncDataExchange::NAME, $report->getIntegration());
}
public function testGetSyncOrder(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncDateTime')
->willReturn($toSyncDateTime);
$syncReport = new ReportDAO(self::INTEGRATION_NAME);
$objectDAO = new ReportObjectDAO($objectName, 2);
$objectDAO->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$objectDAO->addField(new ReportFieldDAO('first_name', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$syncReport->addObject($objectDAO);
// Search for an internal object
$this->syncDataExchange->expects($this->once())
->method('getConflictedInternalObject')
->with($mappingManual, Contact::NAME, $objectDAO)
->willReturn(
new ReportObjectDAO(Contact::NAME, 1)
);
$objectChangeDAO = new ObjectChangeDAO(MauticSyncDataExchange::NAME, Contact::NAME, 1, $objectName, 2);
$objectChangeDAO->addField(new OrderFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$objectChangeDAO->addField(new OrderFieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$this->objectChangeGenerator->expects($this->once())
->method('getSyncObjectChange')
->willReturn($objectChangeDAO);
$syncOrder = $this->createMauticSyncProcess($mappingManual)->getSyncOrder($syncReport);
// The change should have been added to the order as an identified object
$this->assertEquals([Contact::NAME => [1 => $objectChangeDAO]], $syncOrder->getIdentifiedObjects());
}
public function testGetSyncOrderObjectDeleted(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncDateTime')
->willReturn($toSyncDateTime);
$syncReport = new ReportDAO(self::INTEGRATION_NAME);
$reportObjectDAO = new ReportObjectDAO($objectName, 2);
$reportObjectDAO->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$reportObjectDAO->addField(new ReportFieldDAO('first_name', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$syncReport->addObject($reportObjectDAO);
// Search for an internal object
$this->syncDataExchange->expects($this->once())
->method('getConflictedInternalObject')
->with($mappingManual, Contact::NAME, $reportObjectDAO)
->willThrowException(new ObjectDeletedException());
$syncOrder = $this->createMauticSyncProcess($mappingManual)->getSyncOrder($syncReport);
self::assertEquals([], $syncOrder->getIdentifiedObjects());
}
public function testGetSyncOrderObjectSkipped(): void
{
$objectName = 'Contact';
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMapping->addFieldMapping('firstname', 'first_name');
$mappingManual->addObjectMapping($objectMapping);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncDateTime')
->willReturn($toSyncDateTime);
$syncReport = new ReportDAO(self::INTEGRATION_NAME);
$reportObjectDAO = new ReportObjectDAO($objectName, 2);
$reportObjectDAO->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$reportObjectDAO->addField(new ReportFieldDAO('first_name', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$syncReport->addObject($reportObjectDAO);
// Search for an internal object
$this->syncDataExchange->expects($this->once())
->method('getConflictedInternalObject')
->with($mappingManual, Contact::NAME, $reportObjectDAO)
->willReturn(
new ReportObjectDAO(Contact::NAME, 1)
);
$objectChangeDAO = new ObjectChangeDAO(MauticSyncDataExchange::NAME, Contact::NAME, 1, $objectName, 2);
$objectChangeDAO->addField(new OrderFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$objectChangeDAO->addField(new OrderFieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$this->objectChangeGenerator->expects($this->once())
->method('getSyncObjectChange')
->willThrowException(new ObjectSyncSkippedException());
$syncOrder = $this->createMauticSyncProcess($mappingManual)->getSyncOrder($syncReport);
self::assertEquals([], $syncOrder->getIdentifiedObjects());
}
public function testThatItDoesntSyncOtherEntityTypesWhenIDsForSomeEntityAreSpecified(): void
{
$mappingManual = new MappingManualDAO(self::INTEGRATION_NAME);
$this->inputOptionsDAO = new InputOptionsDAO([
'integration' => self::INTEGRATION_NAME,
'mautic-object-id' => ['contact:1'],
]);
$contactMapping = new ObjectMappingDAO(Contact::NAME, 'Contact');
$contactMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$mappingManual->addObjectMapping($contactMapping);
$leadMapping = new ObjectMappingDAO(Contact::NAME, 'Lead');
$leadMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$mappingManual->addObjectMapping($leadMapping);
$companyMapping = new ObjectMappingDAO(Company::NAME, 'Account');
$companyMapping->addFieldMapping('email', 'email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$mappingManual->addObjectMapping($companyMapping);
$fromSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncFromDateTime')
->with(MauticSyncDataExchange::NAME, Contact::NAME)
->willReturn($fromSyncDateTime);
$toSyncDateTime = new \DateTimeImmutable();
$this->syncDateHelper->expects($this->once())
->method('getSyncToDateTime')
->willReturn($toSyncDateTime);
$this->syncDataExchange->expects($this->once())
->method('getSyncReport')
->willReturnCallback(
function (RequestDAO $requestDAO): ReportDAO {
$requestObjects = $requestDAO->getObjects();
$this->assertCount(1, $requestObjects);
/** @var ObjectDAO $requestObject */
$requestObject = $requestObjects[0];
$this->assertEquals(['email'], $requestObject->getRequiredFields());
$this->assertEquals(Contact::NAME, $requestObject->getObject());
return new ReportDAO(self::INTEGRATION_NAME);
}
);
$syncReport = $this->createMauticSyncProcess($mappingManual)->getSyncReport(1);
$this->assertEquals(self::INTEGRATION_NAME, $syncReport->getIntegration());
}
private function createMauticSyncProcess(MappingManualDAO $mappingManualDAO): MauticSyncProcess
{
$mauticSyncProcess = new MauticSyncProcess(
$this->syncDateHelper,
$this->objectChangeGenerator,
);
$mauticSyncProcess->setupSync(
$this->inputOptionsDAO,
$mappingManualDAO,
$this->syncDataExchange
);
return $mauticSyncProcess;
}
}

View File

@@ -0,0 +1,479 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncProcess\Direction\Internal;
use Mautic\IntegrationsBundle\Exception\RequiredValueException;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\ObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InformationChangeRequestDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\FieldDAO as ReportFieldDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ObjectDAO as ReportObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ReportDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\Exception\ObjectSyncSkippedException;
use Mautic\IntegrationsBundle\Sync\Notification\BulkNotification;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Helper\FieldHelper;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\Internal\Object\Contact;
use Mautic\IntegrationsBundle\Sync\SyncJudge\SyncJudgeInterface;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Helper\ValueHelper;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
class ObjectChangeGeneratorTest extends TestCase
{
/**
* @var SyncJudgeInterface&MockObject
*/
private MockObject $syncJudge;
/**
* @var ValueHelper&MockObject
*/
private MockObject $valueHelper;
/**
* @var FieldHelper&MockObject
*/
private MockObject $fieldHelper;
/**
* @var MockObject&BulkNotification
*/
private MockObject $bulkNotification;
protected function setUp(): void
{
$this->syncJudge = $this->createMock(SyncJudgeInterface::class);
$this->valueHelper = $this->createMock(ValueHelper::class);
$this->fieldHelper = $this->createMock(FieldHelper::class);
$this->bulkNotification = $this->createMock(BulkNotification::class);
}
public function testFieldsAreAddedToObjectChangeAndIntegrationFirstNameWins(): void
{
$this->valueHelper->method('getValueForMautic')
->willReturnCallback(
fn (NormalizedValueDAO $normalizedValueDAO, string $fieldState, string $syncDirection) => $normalizedValueDAO
);
$integration = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integration, $objectName);
$syncReport = $this->getIntegrationSyncReport($integration, $objectName);
$internalReportObject = new ReportObjectDAO(Contact::NAME, 1);
$internalReportObject->addField(new ReportFieldDAO('email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')));
$internalReportObject->addField(new ReportFieldDAO('firstname', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')));
$this->syncJudge->expects($this->exactly(2))
->method('adjudicate')
->willReturnCallback(
fn ($mode, InformationChangeRequestDAO $internalInformationChangeRequest, InformationChangeRequestDAO $integrationInformationChangeRequest) => $integrationInformationChangeRequest
);
$objectChangeDAO = $this->createObjectGenerator()->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$internalReportObject,
$syncReport->getObject($objectName, 2)
);
$this->assertEquals($integration, $objectChangeDAO->getIntegration());
// object and object ID should be Mautic's (from the Mautic's POV)
$this->assertEquals(Contact::NAME, $objectChangeDAO->getObject());
$this->assertEquals(1, $objectChangeDAO->getObjectId());
// mapped object and ID should be the integrations
$this->assertEquals($objectName, $objectChangeDAO->getMappedObject());
$this->assertEquals(2, $objectChangeDAO->getMappedObjectId());
// Email should be a required field
$requiredFields = $objectChangeDAO->getRequiredFields();
$this->assertTrue(isset($requiredFields['email']));
// Both fields should be included
$fields = $objectChangeDAO->getFields();
$this->assertTrue(isset($fields['email']) && isset($fields['firstname']));
// First name is presumed to be changed
$changedFields = $objectChangeDAO->getChangedFields();
$this->assertTrue(isset($changedFields['firstname']));
// First name should have changed to Robert because the sync judge returned the integration's information change request
$this->assertEquals('Robert', $changedFields['firstname']->getValue()->getNormalizedValue());
}
public function testFieldsAreAddedToObjectChangeAndInternalFirstNameWins(): void
{
$this->valueHelper->method('getValueForMautic')
->willReturnCallback(
fn (NormalizedValueDAO $normalizedValueDAO, string $fieldState, string $syncDirection) => $normalizedValueDAO
);
$integration = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integration, $objectName);
$syncReport = $this->getIntegrationSyncReport($integration, $objectName);
$internalReportObject = new ReportObjectDAO(Contact::NAME, 1);
$internalReportObject->addField(
new ReportFieldDAO(
'email',
new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com')
)
);
$internalReportObject->addField(
new ReportFieldDAO(
'firstname',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')
)
);
$this->syncJudge->expects($this->exactly(2))
->method('adjudicate')
->willReturnCallback(
fn ($mode, InformationChangeRequestDAO $internalInformationChangeRequest, InformationChangeRequestDAO $integrationInformationChangeRequest) => $internalInformationChangeRequest
);
$objectChangeDAO = $this->createObjectGenerator()->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$internalReportObject,
$syncReport->getObject($objectName, 2)
);
$this->assertEquals($integration, $objectChangeDAO->getIntegration());
// object and object ID should be Mautic's (from the Mautic's POV)
$this->assertEquals(Contact::NAME, $objectChangeDAO->getObject());
$this->assertEquals(1, $objectChangeDAO->getObjectId());
// mapped object and ID should be the integrations
$this->assertEquals($objectName, $objectChangeDAO->getMappedObject());
$this->assertEquals(2, $objectChangeDAO->getMappedObjectId());
// Email should be a required field
$requiredFields = $objectChangeDAO->getRequiredFields();
$this->assertTrue(isset($requiredFields['email']));
// Both fields should be included
$fields = $objectChangeDAO->getFields();
$this->assertTrue(isset($fields['email']) && isset($fields['firstname']));
// First name is presumed to be changed
$changedFields = $objectChangeDAO->getChangedFields();
$this->assertTrue(isset($changedFields['firstname']));
// First name should have changed to Robert because the sync judge returned the integration's information change request
$this->assertEquals('Bob', $changedFields['firstname']->getValue()->getNormalizedValue());
}
public function testRequiredValueRejected(): void
{
$exceptionMessage = 'exceptionMessage';
$this->valueHelper->method('getValueForMautic')
->willThrowException(new RequiredValueException($exceptionMessage));
$integrationName = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integrationName, $objectName);
$syncReport = $this->getIntegrationSyncReport($integrationName, $objectName);
$internalReportObject = new ReportObjectDAO(Contact::NAME, 1);
$internalReportObject->addField(
new ReportFieldDAO(
'email',
new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, '')
)
);
$internalReportObject->addField(
new ReportFieldDAO(
'firstname',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')
)
);
$this->syncJudge->expects($this->exactly(2))
->method('adjudicate')
->willReturnCallback(
function ($mode, InformationChangeRequestDAO $internalInformationChangeRequest, InformationChangeRequestDAO $integrationInformationChangeRequest) {
return $internalInformationChangeRequest;
}
);
$matcher = $this->exactly(2);
$this->bulkNotification->expects($matcher)
->method('addNotification')->willReturnCallback(function (...$parameters) use ($matcher, $exceptionMessage, $integrationName, $objectName) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator-Test-lead-email', $parameters[0]);
$this->assertSame($exceptionMessage, $parameters[1]);
$this->assertSame($integrationName, $parameters[2]);
$this->assertSame($objectName, $parameters[3]);
$this->assertSame(Contact::NAME, $parameters[4]);
$this->assertSame(0, $parameters[5]);
$this->assertSame("Field 'email' for object ID '2' mapped to internal 'email' with value 'test@test.com'", $parameters[6]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator-Test-lead-first_name', $parameters[0]);
$this->assertSame($exceptionMessage, $parameters[1]);
$this->assertSame($integrationName, $parameters[2]);
$this->assertSame($objectName, $parameters[3]);
$this->assertSame(Contact::NAME, $parameters[4]);
$this->assertSame(0, $parameters[5]);
$this->assertSame("Field 'first_name' for object ID '2' mapped to internal 'first_name' with value 'Robert'", $parameters[6]);
}
});
$this->createObjectGenerator()->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$internalReportObject,
$syncReport->getObject($objectName, 2)
);
}
public function testRequiredValueRejectedForExistingObject(): void
{
$exceptionMessage = 'exceptionMessage';
$this->valueHelper->method('getValueForMautic')
->willThrowException(new RequiredValueException($exceptionMessage));
$integrationName = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integrationName, $objectName);
$syncReport = $this->getIntegrationSyncReport($integrationName, $objectName);
$internalReportObject = new ReportObjectDAO(Contact::NAME, 1);
$internalReportObject->addField(
new ReportFieldDAO(
'email',
new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, '')
)
);
$internalReportObject->addField(
new ReportFieldDAO(
'firstname',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Bob')
)
);
$this->syncJudge->expects($this->exactly(2))
->method('adjudicate')
->willReturnCallback(
function ($mode, InformationChangeRequestDAO $internalInformationChangeRequest, InformationChangeRequestDAO $integrationInformationChangeRequest) {
return $internalInformationChangeRequest;
}
);
$matcher = $this->exactly(2);
$this->bulkNotification->expects($matcher)
->method('addNotification')->willReturnCallback(function (...$parameters) use ($matcher, $exceptionMessage, $integrationName, $objectName) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame('Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator-Test-lead-email', $parameters[0]);
$this->assertSame($exceptionMessage, $parameters[1]);
$this->assertSame($integrationName, $parameters[2]);
$this->assertSame($objectName, $parameters[3]);
$this->assertSame(Contact::NAME, $parameters[4]);
$this->assertSame(0, $parameters[5]);
$this->assertSame("Field 'email' for object ID '2' mapped to internal 'email' with value 'test@test.com'", $parameters[6]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame('Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator-Test-lead-first_name', $parameters[0]);
$this->assertSame($exceptionMessage, $parameters[1]);
$this->assertSame($integrationName, $parameters[2]);
$this->assertSame($objectName, $parameters[3]);
$this->assertSame(Contact::NAME, $parameters[4]);
$this->assertSame(0, $parameters[5]);
$this->assertSame("Field 'first_name' for object ID '2' mapped to internal 'first_name' with value 'Robert'", $parameters[6]);
}
});
$this->createObjectGenerator()->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$internalReportObject,
$syncReport->getObject($objectName, 2)
);
}
public function testRequiredValueRejectedForNewObject(): void
{
$exceptionMessage = 'exceptionMessage';
$this->valueHelper->method('getValueForMautic')
->willThrowException(new RequiredValueException($exceptionMessage));
$integrationName = 'Test';
$objectName = 'Contact';
$mappingManual = $this->getMappingManual($integrationName, $objectName);
$syncReport = $this->getIntegrationSyncReport($integrationName, $objectName);
$internalReportObject = new ReportObjectDAO(Contact::NAME, null);
$internalReportObject->addField(
new ReportFieldDAO(
'email',
new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, '')
)
);
$this->syncJudge->expects($this->exactly(1))
->method('adjudicate')
->willReturnCallback(
function ($mode, InformationChangeRequestDAO $internalInformationChangeRequest, InformationChangeRequestDAO $integrationInformationChangeRequest) {
return $internalInformationChangeRequest;
}
);
$this->bulkNotification->expects($this->exactly(1))
->method('addNotification')
->with(
'Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\ObjectChangeGenerator-Test-lead-email',
$exceptionMessage.' New object sync skipped.',
$integrationName,
$objectName,
Contact::NAME,
0,
"Field 'email' for object ID '2' mapped to internal 'email' with value 'test@test.com'"
);
$this->expectException(ObjectSyncSkippedException::class);
$this->createObjectGenerator()->getSyncObjectChange(
$syncReport,
$mappingManual,
$mappingManual->getObjectMapping(Contact::NAME, $objectName),
$internalReportObject,
$syncReport->getObject($objectName, 2)
);
}
public function testFieldsWithDirectionToIntegrationAreSkipped(): void
{
$objectChangeGenerator = new ObjectChangeGenerator(
new class implements SyncJudgeInterface {
public function adjudicate(
$mode,
InformationChangeRequestDAO $leftChangeRequest,
InformationChangeRequestDAO $rightChangeRequest,
) {
return $leftChangeRequest;
}
},
new class extends ValueHelper {
},
new class extends FieldHelper {
public function __construct()
{
}
public function getRequiredFields(string $object): array
{
Assert::assertSame(Contact::NAME, $object);
return ['email' => []];
}
},
new class extends BulkNotification {
public function __construct()
{
}
}
);
$integrationName = 'Integration A';
$reportDAO = new ReportDAO($integrationName);
$mappingManualDAO = new MappingManualDAO($integrationName);
$objectMappingDAO = new ObjectMappingDAO(Contact::NAME, 'Lead');
$internalObject = new ReportObjectDAO(Contact::NAME, 123);
$integrationObject = new ReportObjectDAO('Lead', 'integration-id-1');
$objectMappingDAO->addFieldMapping('email', 'Email', ObjectMappingDAO::SYNC_BIDIRECTIONALLY, true);
$objectMappingDAO->addFieldMapping('firstname', 'FirstName', ObjectMappingDAO::SYNC_TO_MAUTIC);
$objectMappingDAO->addFieldMapping('points', 'Score', ObjectMappingDAO::SYNC_TO_INTEGRATION);
$integrationObject->addField(new ReportFieldDAO('Email', new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'john@doe.email')));
$integrationObject->addField(new ReportFieldDAO('FirstName', new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'John')));
$integrationObject->addField(new ReportFieldDAO('Score', new NormalizedValueDAO(NormalizedValueDAO::INT_TYPE, 40)));
$reportDAO->addObject($integrationObject);
$objectChange = $objectChangeGenerator->getSyncObjectChange($reportDAO, $mappingManualDAO, $objectMappingDAO, $internalObject, $integrationObject);
// The points/Score field should not be recorded as a change because it has direction to integration.
Assert::assertCount(2, $objectChange->getFields());
Assert::assertSame('john@doe.email', $objectChange->getField('email')->getValue()->getNormalizedValue());
Assert::assertSame('John', $objectChange->getField('firstname')->getValue()->getNormalizedValue());
Assert::assertSame('Lead', $objectChange->getMappedObject());
Assert::assertSame('integration-id-1', $objectChange->getMappedObjectId());
Assert::assertSame(Contact::NAME, $objectChange->getObject());
Assert::assertSame(123, $objectChange->getObjectId());
Assert::assertSame($integrationName, $objectChange->getIntegration());
}
private function getMappingManual(string $integration, string $objectName): MappingManualDAO
{
$mappingManual = new MappingManualDAO($integration);
$objectMapping = new ObjectMappingDAO(Contact::NAME, $objectName);
$objectMapping->addFieldMapping(
'email',
'email',
ObjectMappingDAO::SYNC_BIDIRECTIONALLY,
true);
$objectMapping->addFieldMapping(
'firstname',
'first_name'
);
$mappingManual->addObjectMapping($objectMapping);
return $mappingManual;
}
private function getIntegrationSyncReport(string $integration, string $objectName): ReportDAO
{
$syncReport = new ReportDAO($integration);
$reportObject = new ReportObjectDAO($objectName, 2);
$reportObject->addField(
new ReportFieldDAO(
'email',
new NormalizedValueDAO(NormalizedValueDAO::EMAIL_TYPE, 'test@test.com'),
ReportFieldDAO::FIELD_REQUIRED
)
);
$reportObject->addField(
new ReportFieldDAO(
'first_name',
new NormalizedValueDAO(NormalizedValueDAO::TEXT_TYPE, 'Robert')
)
);
$syncReport->addObject($reportObject);
return $syncReport;
}
private function createObjectGenerator(): ObjectChangeGenerator
{
return new ObjectChangeGenerator(
$this->syncJudge,
$this->valueHelper,
$this->fieldHelper,
$this->bulkNotification
);
}
}

View File

@@ -0,0 +1,278 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\SyncProcess;
use Mautic\IntegrationsBundle\Entity\ObjectMapping;
use Mautic\IntegrationsBundle\Event\CompletedSyncIterationEvent;
use Mautic\IntegrationsBundle\IntegrationEvents;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\MappingManualDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\RemappedObjectDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Mapping\UpdatedObjectMappingDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\InputOptionsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectChangeDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\ObjectMappingsDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Order\OrderDAO;
use Mautic\IntegrationsBundle\Sync\DAO\Sync\Report\ReportDAO;
use Mautic\IntegrationsBundle\Sync\Helper\MappingHelper;
use Mautic\IntegrationsBundle\Sync\Helper\RelationsHelper;
use Mautic\IntegrationsBundle\Sync\Helper\SyncDateHelper;
use Mautic\IntegrationsBundle\Sync\Notification\Notifier;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\MauticSyncDataExchange;
use Mautic\IntegrationsBundle\Sync\SyncDataExchange\SyncDataExchangeInterface;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Integration\IntegrationSyncProcess;
use Mautic\IntegrationsBundle\Sync\SyncProcess\Direction\Internal\MauticSyncProcess;
use Mautic\IntegrationsBundle\Sync\SyncProcess\SyncProcess;
use Mautic\IntegrationsBundle\Sync\SyncService\SyncServiceInterface;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
class SyncProcessTest extends TestCase
{
/**
* @var MockObject|MappingManualDAO
*/
private MockObject $mappingManualDAO;
/**
* @var MockObject|MauticSyncDataExchange
*/
private MockObject $internalSyncDataExchange;
/**
* @var MockObject|SyncDataExchangeInterface
*/
private MockObject $integrationSyncDataExchange;
/**
* @var MockObject|SyncDateHelper
*/
private MockObject $syncDateHelper;
/**
* @var MockObject|MappingHelper
*/
private MockObject $mappingHelper;
/**
* @var MockObject|RelationsHelper
*/
private MockObject $relationsHelper;
/**
* @var MockObject|IntegrationSyncProcess
*/
private MockObject $integrationSyncProcess;
/**
* @var MockObject|MauticSyncProcess
*/
private MockObject $mauticSyncProcess;
/**
* @var MockObject|EventDispatcherInterface
*/
private MockObject $eventDispatcher;
/**
* @var MockObject|Notifier
*/
private MockObject $notifier;
/**
* @var MockObject|InputOptionsDAO
*/
private MockObject $inputOptionsDAO;
/**
* @var MockObject|SyncServiceInterface
*/
private MockObject $syncService;
private SyncProcess $syncProcess;
protected function setUp(): void
{
$this->syncDateHelper = $this->createMock(SyncDateHelper::class);
$this->mappingHelper = $this->createMock(MappingHelper::class);
$this->relationsHelper = $this->createMock(RelationsHelper::class);
$this->integrationSyncProcess = $this->createMock(IntegrationSyncProcess::class);
$this->mauticSyncProcess = $this->createMock(MauticSyncProcess::class);
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->notifier = $this->createMock(Notifier::class);
$this->mappingManualDAO = $this->createMock(MappingManualDAO::class);
$this->integrationSyncDataExchange = $this->createMock(SyncDataExchangeInterface::class);
$this->internalSyncDataExchange = $this->createMock(MauticSyncDataExchange::class);
$this->inputOptionsDAO = $this->createMock(InputOptionsDAO::class);
$this->syncService = $this->createMock(SyncServiceInterface::class);
$this->syncProcess = new SyncProcess(
$this->syncDateHelper,
$this->mappingHelper,
$this->relationsHelper,
$this->integrationSyncProcess,
$this->mauticSyncProcess,
$this->eventDispatcher,
$this->notifier,
$this->mappingManualDAO,
$this->internalSyncDataExchange,
$this->integrationSyncDataExchange,
$this->inputOptionsDAO,
$this->syncService
);
}
public function testBatchSyncEventsAreDispatched(): void
{
$this->inputOptionsDAO->expects($this->once())
->method('pullIsEnabled')
->willReturn(true);
$this->inputOptionsDAO->expects($this->once())
->method('pushIsEnabled')
->willReturn(true);
$this->syncDateHelper->expects($this->once())
->method('setInternalSyncStartDateTime');
// Integration to Mautic
// fetch the report from the integration
$integrationSyncReport = $this->createMock(ReportDAO::class);
$integrationSyncReport->expects($this->exactly(2))
->method('shouldSync')
->willReturnOnConsecutiveCalls(true, false);
$matcher = $this->exactly(2);
$this->integrationSyncProcess->expects($matcher)
->method('getSyncReport')->willReturnCallback(function (...$parameters) use ($matcher, $integrationSyncReport) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(1, $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(2, $parameters[0]);
}
return $integrationSyncReport;
});
// generate the order based on the report
$integrationSyncOrder = $this->createMock(OrderDAO::class);
$integrationSyncOrder->expects($this->once())
->method('shouldSync')
->willReturn(true);
$this->mauticSyncProcess->expects($this->once())
->method('getSyncOrder')
->with($integrationSyncReport)
->willReturn($integrationSyncOrder);
$integrationSyncOrder->expects($this->once())
->method('getDeletedObjects')
->willReturn([new ObjectChangeDAO('foobar', 'foo', 'foo1', 'contact', 1)]);
$integrationSyncOrder->expects($this->once())
->method('getRemappedObjects')
->willReturn([new RemappedObjectDAO('foobar', 'foo', 'foo1', 'bar', 'bar1')]);
// execute the order
$objectMappings = $this->createMock(ObjectMappingsDAO::class);
$objectMappings->expects($this->once())
->method('getNewMappings')
->willReturn([(new ObjectMapping())->setIntegrationObjectName('foo')]);
$objectMappings->expects($this->once())
->method('getUpdatedMappings')
->willReturn([(new ObjectMapping())->setIntegrationObjectName('bar')]);
$this->internalSyncDataExchange->expects($this->once())
->method('executeSyncOrder')
->willReturn($objectMappings);
$matcher = $this->any();
$this->eventDispatcher->expects($matcher)
->method('dispatch')->willReturnCallback(function (...$parameters) use ($matcher) {
if (1 === $matcher->numberOfInvocations()) {
$callback = function (CompletedSyncIterationEvent $event) {
$orderResult = $event->getOrderResults();
Assert::assertCount(1, $orderResult->getUpdatedObjectMappings('bar'));
Assert::assertCount(1, $orderResult->getNewObjectMappings('foo'));
Assert::assertCount(1, $orderResult->getDeletedObjects('foo'));
Assert::assertCount(1, $orderResult->getRemappedObjects('bar'));
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_BATCH_SYNC_COMPLETED_INTEGRATION_TO_MAUTIC, $parameters[1]);
}
if (2 === $matcher->numberOfInvocations()) {
$callback = function (CompletedSyncIterationEvent $event) {
$orderResult = $event->getOrderResults();
Assert::assertCount(1, $orderResult->getNewObjectMappings('bar'));
Assert::assertCount(1, $orderResult->getUpdatedObjectMappings('foo'));
};
$callback($parameters[0]);
$this->assertSame(IntegrationEvents::INTEGRATION_BATCH_SYNC_COMPLETED_MAUTIC_TO_INTEGRATION, $parameters[1]);
}
return $parameters[0];
});
// Mautic to integration
// fetch the report from Mautic
$internalSyncReport = $this->createMock(ReportDAO::class);
$internalSyncReport->expects($this->exactly(2))
->method('shouldSync')
->willReturnOnConsecutiveCalls(true, false);
$matcher = $this->exactly(2);
$this->mauticSyncProcess->expects($matcher)
->method('getSyncReport')->willReturnCallback(function (...$parameters) use ($matcher, $internalSyncReport) {
if (1 === $matcher->numberOfInvocations()) {
$this->assertSame(1, $parameters[0]);
}
if (2 === $matcher->numberOfInvocations()) {
$this->assertSame(2, $parameters[0]);
}
return $internalSyncReport;
});
// generate the order based on the report
$internalSyncOrder = $this->createMock(OrderDAO::class);
$internalSyncOrder->expects($this->once())
->method('shouldSync')
->willReturnOnConsecutiveCalls(true);
$internalSyncOrder->expects($this->exactly(2))
->method('getObjectMappings')
->willReturn([(new ObjectMapping())->setIntegrationObjectName('bar')]);
$updatedObjectMapping = new UpdatedObjectMappingDAO('foobar', 'foo', 'foo1', new \DateTime());
$updatedObjectMapping->setObjectMapping((new ObjectMapping())->setIntegrationObjectName('foo'));
// Test that getOrderResultsForInternalSync ignores an object with a missing ObjectMapping
$updatedObjectMapping2 = new UpdatedObjectMappingDAO('foobar', 'foo', 'foo2', new \DateTime());
$internalSyncOrder->expects($this->exactly(2))
->method('getUpdatedObjectMappings')
->willReturn([$updatedObjectMapping, $updatedObjectMapping2]);
$internalSyncOrder->expects($this->exactly(2))
->method('getDeletedObjects')
->willReturn([]); // currently not supported for Mautic to integration
$internalSyncOrder->expects($this->exactly(2))
->method('getRemappedObjects')
->willReturn([]); // currently not supported for Mautic to integration
$internalSyncOrder->expects($this->once())
->method('getNotifications')
->willReturn([]);
$internalSyncOrder->expects($this->once())
->method('getSuccessfullySyncedObjects')
->willReturn([]);
$this->integrationSyncProcess->expects($this->once())
->method('getSyncOrder')
->with($internalSyncReport)
->willReturn($internalSyncOrder);
// execute the order
$this->internalSyncDataExchange->expects($this->once())
->method('executeSyncOrder')
->willReturn($objectMappings);
$this->syncProcess->execute();
}
}

View File

@@ -0,0 +1,31 @@
<?php
declare(strict_types=1);
namespace Mautic\IntegrationsBundle\Tests\Unit\Sync\ValueNormalizer;
use Mautic\IntegrationsBundle\Sync\DAO\Value\NormalizedValueDAO;
use Mautic\IntegrationsBundle\Sync\ValueNormalizer\ValueNormalizer;
use PHPUnit\Framework\TestCase;
class ValueNormalizerTest extends TestCase
{
public function testNullDateTimeValue(): void
{
$valueNormalizer = new ValueNormalizer();
$normalizedValueDAO = $valueNormalizer->normalizeForMautic(NormalizedValueDAO::DATETIME_TYPE, null);
$this->assertNull($normalizedValueDAO->getNormalizedValue());
$this->assertNull($normalizedValueDAO->getOriginalValue());
}
public function testNotNullDateTimeValue(): void
{
$valueNormalizer = new ValueNormalizer();
$normalizedValueDAO = $valueNormalizer->normalizeForMautic(NormalizedValueDAO::DATETIME_TYPE, '2019-10-08');
$this->assertInstanceOf(\DateTimeInterface::class, $normalizedValueDAO->getNormalizedValue());
$this->assertSame('2019-10-08', $normalizedValueDAO->getNormalizedValue()->format('Y-m-d'));
$this->assertSame('2019-10-08', $normalizedValueDAO->getOriginalValue());
}
}