Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Form\Type;
|
||||
|
||||
use Mautic\NotificationBundle\Form\Type\MobileNotificationDetailsType;
|
||||
use Mautic\PluginBundle\Entity\Integration;
|
||||
use Mautic\PluginBundle\Helper\IntegrationHelper;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
|
||||
use Symfony\Component\Form\FormExtensionInterface;
|
||||
use Symfony\Component\Form\PreloadedExtension;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
|
||||
class MobileNotificationDetailsTypeTest extends TypeTestCase
|
||||
{
|
||||
/**
|
||||
* @var MockObject&Integration
|
||||
*/
|
||||
private MockObject $integrationSettings;
|
||||
|
||||
/**
|
||||
* @return array<FormExtensionInterface>
|
||||
*/
|
||||
protected function getExtensions(): array
|
||||
{
|
||||
$validatorBuilder = Validation::createValidatorBuilder();
|
||||
$validatorBuilder->addMethodMapping('loadValidatorMetadata');
|
||||
|
||||
$this->integrationSettings = $this->createMock(Integration::class);
|
||||
|
||||
// @phpstan-ignore-next-line
|
||||
$integration = $this->createMock(AbstractIntegration::class);
|
||||
$integration->method('getIntegrationSettings')
|
||||
->willReturn($this->integrationSettings);
|
||||
|
||||
$integrationHelper = $this->createMock(IntegrationHelper::class);
|
||||
$integrationHelper->method('getIntegrationObject')
|
||||
->with('OneSignal')
|
||||
->willReturn($integration);
|
||||
|
||||
return [
|
||||
new ValidatorExtension($validatorBuilder->getValidator()),
|
||||
new PreloadedExtension([
|
||||
new MobileNotificationDetailsType($integrationHelper),
|
||||
], []),
|
||||
];
|
||||
}
|
||||
|
||||
public function testNoPlatformsSelected(): void
|
||||
{
|
||||
$this->integrationSettings->method('getFeatureSettings')
|
||||
->willReturn([]);
|
||||
|
||||
$form = $this->factory->create(MobileNotificationDetailsType::class);
|
||||
|
||||
$view = $form->createView();
|
||||
// test only field is "additional_data"
|
||||
self::assertCount(1, $view->children);
|
||||
self::assertArrayHasKey('additional_data', $view->children);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $platforms
|
||||
* @param array<int, string> $settings
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('platformProvider')]
|
||||
public function testPlatformSelected(array $platforms, array $settings): void
|
||||
{
|
||||
$this->integrationSettings->method('getFeatureSettings')
|
||||
->willReturn(['platforms' => $platforms]);
|
||||
|
||||
$form = $this->factory->create(MobileNotificationDetailsType::class);
|
||||
|
||||
$view = $form->createView();
|
||||
self::assertCount(1 + count($settings), $view->children);
|
||||
self::assertArrayHasKey('additional_data', $view->children);
|
||||
|
||||
foreach ($settings as $settingField) {
|
||||
self::assertArrayHasKey($settingField, $view->children);
|
||||
}
|
||||
}
|
||||
|
||||
public static function platformProvider(): \Generator
|
||||
{
|
||||
$iosSettings = [
|
||||
'ios_subtitle',
|
||||
'ios_sound',
|
||||
'ios_badges',
|
||||
'ios_badgeCount',
|
||||
'ios_contentAvailable',
|
||||
'ios_media',
|
||||
'ios_mutableContent',
|
||||
];
|
||||
|
||||
$androidSettings = [
|
||||
'android_sound',
|
||||
'android_small_icon',
|
||||
'android_large_icon',
|
||||
'android_big_picture',
|
||||
'android_led_color',
|
||||
'android_accent_color',
|
||||
'android_group_key',
|
||||
'android_lockscreen_visibility',
|
||||
];
|
||||
|
||||
yield 'ios' => [['ios'], $iosSettings];
|
||||
yield 'android' => [['android'], $androidSettings];
|
||||
yield 'both' => [['android', 'ios'], array_merge($androidSettings, $iosSettings)];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Form\Type;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Form\Type\EntityLookupType;
|
||||
use Mautic\NotificationBundle\Form\Type\MobileNotificationSendType;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
|
||||
use Symfony\Component\Form\PreloadedExtension;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class MobileNotificationSendTypeTest extends TypeTestCase
|
||||
{
|
||||
private RouterInterface $router;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private Connection $connection;
|
||||
|
||||
/**
|
||||
* @var ModelFactory<object>&MockObject
|
||||
*/
|
||||
private ModelFactory $modelFactory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->router = $this->createMock(RouterInterface::class);
|
||||
$this->translator = $this->createMock(TranslatorInterface::class);
|
||||
$this->modelFactory = $this->createMock(ModelFactory::class);
|
||||
$this->connection = $this->createMock(Connection::class);
|
||||
|
||||
parent::setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
protected function getExtensions()
|
||||
{
|
||||
return [
|
||||
new ValidatorExtension(Validation::createValidator()),
|
||||
new PreloadedExtension([
|
||||
new MobileNotificationSendType($this->router),
|
||||
new EntityLookupType($this->modelFactory, $this->translator, $this->connection, $this->router),
|
||||
], []),
|
||||
];
|
||||
}
|
||||
|
||||
public function testSubmitValidData(): void
|
||||
{
|
||||
$form = $this->factory->create(MobileNotificationSendType::class);
|
||||
|
||||
$expected = [
|
||||
'notification' => '1',
|
||||
];
|
||||
|
||||
$form->submit([
|
||||
'notification' => '1',
|
||||
]);
|
||||
|
||||
// This check ensures there are no transformation failures
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
// check that $model was modified as expected when the form was submitted
|
||||
$this->assertEquals($expected, $form->getData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Form\Type;
|
||||
|
||||
use Doctrine\DBAL\Connection;
|
||||
use Mautic\CoreBundle\Factory\ModelFactory;
|
||||
use Mautic\CoreBundle\Form\Type\EntityLookupType;
|
||||
use Mautic\NotificationBundle\Form\Type\NotificationSendType;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
|
||||
use Symfony\Component\Form\PreloadedExtension;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
final class NotificationSendTypeTest extends TypeTestCase
|
||||
{
|
||||
private RouterInterface $router;
|
||||
|
||||
private TranslatorInterface $translator;
|
||||
|
||||
private Connection $connection;
|
||||
|
||||
/**
|
||||
* @var ModelFactory<object>&MockObject
|
||||
*/
|
||||
private ModelFactory $modelFactory;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->router = $this->createMock(RouterInterface::class);
|
||||
$this->translator = $this->createMock(TranslatorInterface::class);
|
||||
$this->modelFactory = $this->createMock(ModelFactory::class);
|
||||
$this->connection = $this->createMock(Connection::class);
|
||||
|
||||
parent::setup();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
protected function getExtensions()
|
||||
{
|
||||
return [
|
||||
new ValidatorExtension(Validation::createValidator()),
|
||||
new PreloadedExtension([
|
||||
new NotificationSendType($this->router),
|
||||
new EntityLookupType($this->modelFactory, $this->translator, $this->connection, $this->router),
|
||||
], []),
|
||||
];
|
||||
}
|
||||
|
||||
public function testSubmitValidData(): void
|
||||
{
|
||||
$form = $this->factory->create(NotificationSendType::class);
|
||||
|
||||
$expected = [
|
||||
'notification' => '1',
|
||||
];
|
||||
|
||||
$form->submit([
|
||||
'notification' => '1',
|
||||
]);
|
||||
|
||||
// This check ensures there are no transformation failures
|
||||
$this->assertTrue($form->isSynchronized());
|
||||
|
||||
// check that $model was modified as expected when the form was submitted
|
||||
$this->assertEquals($expected, $form->getData());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Form\Type;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\CategoryBundle\Form\Type\CategoryListType;
|
||||
use Mautic\CategoryBundle\Model\CategoryModel;
|
||||
use Mautic\NotificationBundle\Entity\Notification;
|
||||
use Mautic\NotificationBundle\Form\Type\NotificationType;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\Form\Extension\Validator\ValidatorExtension;
|
||||
use Symfony\Component\Form\FormErrorIterator;
|
||||
use Symfony\Component\Form\FormExtensionInterface;
|
||||
use Symfony\Component\Form\PreloadedExtension;
|
||||
use Symfony\Component\Form\Test\TypeTestCase;
|
||||
use Symfony\Component\Routing\RouterInterface;
|
||||
use Symfony\Component\Validator\Validation;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class NotificationTypeTest extends TypeTestCase
|
||||
{
|
||||
/**
|
||||
* @return array<FormExtensionInterface>
|
||||
*/
|
||||
protected function getExtensions(): array
|
||||
{
|
||||
$validatorBuilder = Validation::createValidatorBuilder();
|
||||
$validatorBuilder->addMethodMapping('loadValidatorMetadata');
|
||||
|
||||
return [
|
||||
new ValidatorExtension($validatorBuilder->getValidator()),
|
||||
new PreloadedExtension([
|
||||
new CategoryListType(
|
||||
$this->createMock(EntityManager::class),
|
||||
$this->createMock(TranslatorInterface::class),
|
||||
$this->createMock(CategoryModel::class),
|
||||
$this->createMock(RouterInterface::class),
|
||||
),
|
||||
], []),
|
||||
];
|
||||
}
|
||||
|
||||
public function testSubmitInvalidData(): void
|
||||
{
|
||||
$form = $this->factory->create(NotificationType::class);
|
||||
|
||||
$expected = new Notification();
|
||||
$expected->setLanguage('en');
|
||||
$expected->setUtmTags([
|
||||
'utmSource' => null,
|
||||
'utmMedium' => null,
|
||||
'utmCampaign' => null,
|
||||
'utmContent' => null,
|
||||
]);
|
||||
$expected->setIsPublished(false);
|
||||
|
||||
$form->submit([
|
||||
'language' => 'en',
|
||||
]);
|
||||
|
||||
Assert::assertTrue($form->isSynchronized());
|
||||
|
||||
$formData = $form->getData();
|
||||
\assert($formData instanceof Notification);
|
||||
|
||||
$expected->setChanges($formData->getChanges());
|
||||
Assert::assertEquals($expected, $formData);
|
||||
|
||||
Assert::assertFalse($form->isValid());
|
||||
|
||||
$view = $form->createView();
|
||||
$invalidFields = ['name', 'heading', 'message'];
|
||||
$errorCount = 0;
|
||||
foreach ($view->children as $fieldName => $child) {
|
||||
$errors = $view->children[$fieldName]->vars['errors'];
|
||||
\assert($errors instanceof FormErrorIterator);
|
||||
|
||||
if (in_array($fieldName, $invalidFields, true)) {
|
||||
++$errorCount;
|
||||
self::assertCount(1, $errors);
|
||||
continue;
|
||||
}
|
||||
|
||||
self::assertCount(0, $errors);
|
||||
}
|
||||
|
||||
self::assertCount($errorCount, $invalidFields);
|
||||
self::assertCount(0, $view->vars['errors']);
|
||||
}
|
||||
|
||||
public function testSubmitValidData(): void
|
||||
{
|
||||
$form = $this->factory->create(NotificationType::class);
|
||||
|
||||
$expected = new Notification();
|
||||
$expected->setLanguage('en');
|
||||
$expected->setName('The name');
|
||||
$expected->setHeading('The heading');
|
||||
$expected->setMessage('The message');
|
||||
$expected->setUtmTags([
|
||||
'utmSource' => null,
|
||||
'utmMedium' => null,
|
||||
'utmCampaign' => null,
|
||||
'utmContent' => null,
|
||||
]);
|
||||
$expected->setIsPublished(false);
|
||||
|
||||
$form->submit([
|
||||
'name' => 'The name',
|
||||
'heading' => 'The heading',
|
||||
'message' => 'The message',
|
||||
'language' => 'en',
|
||||
]);
|
||||
|
||||
Assert::assertTrue($form->isSynchronized());
|
||||
|
||||
$formData = $form->getData();
|
||||
\assert($formData instanceof Notification);
|
||||
|
||||
$expected->setChanges($formData->getChanges());
|
||||
Assert::assertEquals($expected, $formData);
|
||||
|
||||
Assert::assertTrue($form->isValid());
|
||||
|
||||
$view = $form->createView();
|
||||
foreach ($view->children as $fieldName => $child) {
|
||||
$errors = $view->children[$fieldName]->vars['errors'];
|
||||
\assert($errors instanceof FormErrorIterator);
|
||||
self::assertCount(0, $errors);
|
||||
}
|
||||
|
||||
self::assertCount(0, $view->vars['errors']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class MobileNotificationControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
/**
|
||||
* Smoke test to ensure the '/s/mobile_notifications' route loads.
|
||||
*/
|
||||
public function testIndexRouteSuccessfullyLoads(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/s/mobile_notifications');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testCreateRouteSuccessfullyLoads(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/s/mobile_notifications/new');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\NotificationBundle\Entity\Notification;
|
||||
use Mautic\NotificationBundle\Entity\Stat;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
|
||||
final class MobileNotificationTranslationFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testNotificationCanBeCreatedWithTranslationParent(): void
|
||||
{
|
||||
// Arrange
|
||||
$parentNotification = $this->createAndPersistNotification('Parent Notification', 'Parent Notification message');
|
||||
|
||||
// Act
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications/new');
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['mobile_notification[name]'] = 'Child Notification';
|
||||
$form['mobile_notification[message]'] = 'Child Notification message';
|
||||
$form['mobile_notification[heading]'] = 'Child Notification';
|
||||
$form['mobile_notification[translationParentSelector]'] = (string) $parentNotification->getId();
|
||||
|
||||
$this->client->submit($form);
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Assert
|
||||
$childNotification = $this->em->getRepository(Notification::class)->findOneBy(['name' => 'Child Notification']);
|
||||
$this->assertInstanceOf(Notification::class, $childNotification);
|
||||
$this->assertInstanceOf(Notification::class, $childNotification->getTranslationParent());
|
||||
$this->assertSame($parentNotification->getId(), $childNotification->getTranslationParent()->getId());
|
||||
}
|
||||
|
||||
public function testNotificationCannotBeItsOwnTranslationParent(): void
|
||||
{
|
||||
// Arrange
|
||||
$notification = $this->createAndPersistNotification('Test Notification', 'Test Notification message');
|
||||
|
||||
// Act
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications/edit/'.$notification->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Assert
|
||||
$options = $crawler->filter('#mobile_notification_translationParentSelector option');
|
||||
$this->assertCount(2, $options);
|
||||
$this->assertSame('Choose a translated item...', $options->eq(0)->text());
|
||||
$this->assertSame('Create new...', $options->eq(1)->text());
|
||||
|
||||
// Ensure the Notification itself is not in the dropdown
|
||||
$optionValues = $options->each(fn ($node) => $node->attr('value'));
|
||||
$this->assertNotContains((string) $notification->getId(), $optionValues);
|
||||
}
|
||||
|
||||
public function testNotificationWithTranslationParentCanBeEdited(): void
|
||||
{
|
||||
// Arrange
|
||||
$parentNotification = $this->createAndPersistNotification('Parent Notification', 'Parent Notification message');
|
||||
$childNotification = $this->createAndPersistNotification('Child Notification', 'Child Notification message');
|
||||
$childNotification->setTranslationParent($parentNotification);
|
||||
|
||||
$newParentNotification = $this->createAndPersistNotification('New Parent Notification', 'New Parent Notification message');
|
||||
$this->em->flush();
|
||||
|
||||
// Act
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications/edit/'.$childNotification->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Assert original parent is selected
|
||||
$this->assertSame(
|
||||
(string) $parentNotification->getId(),
|
||||
$crawler->filter('#mobile_notification_translationParentSelector option[selected]')->attr('value')
|
||||
);
|
||||
|
||||
// Change parent
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['mobile_notification[translationParentSelector]'] = (string) $newParentNotification->getId();
|
||||
$this->client->submit($form);
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Assert parent updated
|
||||
$this->em->refresh($childNotification);
|
||||
$this->assertInstanceOf(Notification::class, $childNotification->getTranslationParent());
|
||||
$this->assertSame($newParentNotification->getId(), $childNotification->getTranslationParent()->getId());
|
||||
}
|
||||
|
||||
public function testTranslationParentCanBeRemovedFromNotification(): void
|
||||
{
|
||||
// Arrange
|
||||
$parentNotification = $this->createAndPersistNotification('Parent Notification', 'Parent Notification message');
|
||||
$childNotification = $this->createAndPersistNotification('Child Notification', 'Child Notification message');
|
||||
$childNotification->setTranslationParent($parentNotification);
|
||||
$this->em->flush();
|
||||
|
||||
// Act
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications/edit/'.$childNotification->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['mobile_notification[translationParentSelector]'] = '';
|
||||
$this->client->submit($form);
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
// Assert
|
||||
$this->em->refresh($childNotification);
|
||||
$this->assertNull($childNotification->getTranslationParent());
|
||||
}
|
||||
|
||||
public function testTranslationsAreDisplayedOnViewPage(): void
|
||||
{
|
||||
// Arrange
|
||||
$parentNotification = $this->createAndPersistNotification('Parent Notification', 'Parent Notification message', 'en');
|
||||
$childNotification = $this->createAndPersistNotification('Child Notification', 'Child Notification message', 'fr');
|
||||
$childNotification->setTranslationParent($parentNotification);
|
||||
$parentNotification->addTranslationChild($childNotification);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
// Act & Assert - Parent view
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications/view/'.$parentNotification->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertCount(1, $crawler->filter('a[href="#translation-container"]'));
|
||||
$this->client->click($crawler->selectLink('Translations')->link());
|
||||
$this->assertSelectorTextContains('#translation-container', 'Child Notification');
|
||||
|
||||
// Act & Assert - Child view
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications/view/'.$childNotification->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertCount(1, $crawler->filter('a[href="#translation-container"]'));
|
||||
$this->client->click($crawler->selectLink('Translations')->link());
|
||||
$this->assertSelectorTextContains('#translation-container', 'Parent Notification');
|
||||
}
|
||||
|
||||
public function testListPageWithSentStats(): void
|
||||
{
|
||||
// Arrange
|
||||
$parentNotification = $this->createAndPersistNotification('Parent Notification', 'Parent Notification message', 'en');
|
||||
$childNotification = $this->createAndPersistNotification('Child Notification', 'Child Notification message', 'fr');
|
||||
$childNotification->setTranslationParent($parentNotification);
|
||||
$parentNotification->addTranslationChild($childNotification);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
// Create a stat
|
||||
$this->createStatEntry($parentNotification, $this->createContact('user', 'one'));
|
||||
$this->createStatEntry($childNotification, $this->createContact('user', 'two'));
|
||||
|
||||
// Act & Assert - list view
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/mobile_notifications');
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertCount(2, $crawler->filterXPath("//td[contains(@class, 'col-stats')]"));
|
||||
}
|
||||
|
||||
private function createANotification(string $name, string $message, bool $isPublished = true, string $locale = 'en'): Notification
|
||||
{
|
||||
$notification = new Notification();
|
||||
$notification->setName($name);
|
||||
$notification->setMessage($message);
|
||||
$notification->setHeading($name);
|
||||
$notification->setLanguage($locale);
|
||||
$notification->setIsPublished($isPublished);
|
||||
$notification->setMobile(true);
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
private function createAndPersistNotification(string $name, string $message, string $locale = 'en'): Notification
|
||||
{
|
||||
$notification = $this->createANotification($name, $message, true, $locale);
|
||||
$this->em->persist($notification);
|
||||
$this->em->flush();
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
public function createStatEntry(Notification $notification, Lead $lead): void
|
||||
{
|
||||
$stat = new Stat();
|
||||
$stat->setDateSent(new \DateTime());
|
||||
$stat->setLead($lead);
|
||||
$stat->setNotification($notification);
|
||||
$stat->setSource(null);
|
||||
$stat->setSourceId(null);
|
||||
|
||||
$this->em->persist($stat);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
private function createContact(string $firstname, string $lastname): Lead
|
||||
{
|
||||
$contact = new Lead();
|
||||
$contact->setFirstname($firstname);
|
||||
$contact->setLastname($lastname);
|
||||
$this->em->persist($contact);
|
||||
$this->em->flush();
|
||||
|
||||
return $contact;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\NotificationBundle\Tests\NotificationTrait;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\DomCrawler\Crawler;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class NotificationControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
use NotificationTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const REST_API_ID = 'restApiID';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const API_ID = 'apiID';
|
||||
|
||||
/**
|
||||
* Smoke test to ensure the '/s/notifications' route loads.
|
||||
*/
|
||||
public function testIndexRouteSuccessfullyLoads(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/s/notifications');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* Smoke test to ensure the '/s/notifications/new' route loads.
|
||||
*/
|
||||
public function testNewRouteSuccessfullyLoads(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/s/notifications/new');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
}
|
||||
|
||||
public function testNewWebNotificationValidSubmit(): void
|
||||
{
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/notifications/new');
|
||||
$formCrawler = $crawler->filter('form[name=notification]');
|
||||
$this->assertCount(1, $formCrawler);
|
||||
|
||||
$form = $formCrawler->form();
|
||||
$form->setValues([
|
||||
'notification[name]' => 'Some Name',
|
||||
'notification[heading]' => 'Some Heading',
|
||||
'notification[message]' => 'some message',
|
||||
]);
|
||||
$crawler = $this->client->submit($form);
|
||||
|
||||
Assert::assertStringContainsString('Some Name has been created!', $crawler->text());
|
||||
}
|
||||
|
||||
public function testNewWebNotificationValidationErrors(): void
|
||||
{
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/notifications/new');
|
||||
$this->assertValidationErrors($crawler);
|
||||
}
|
||||
|
||||
public function testEditWebNotificationValidationErrors(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$crawler = $this->client->request(Request::METHOD_GET, '/s/notifications/edit/'.$notification->getid());
|
||||
$this->assertValidationErrors($crawler);
|
||||
}
|
||||
|
||||
private function assertValidationErrors(Crawler $crawler): void
|
||||
{
|
||||
$formCrawler = $crawler->filter('form[name=notification]');
|
||||
$this->assertCount(1, $formCrawler);
|
||||
|
||||
// test blank errors
|
||||
$form = $formCrawler->form();
|
||||
$form->setValues([
|
||||
'notification[name]' => '',
|
||||
'notification[heading]' => '',
|
||||
'notification[message]' => '',
|
||||
]);
|
||||
$crawler = $this->client->submit($form);
|
||||
$formCrawler = $crawler->filter('form[name=notification]');
|
||||
$this->assertCount(1, $formCrawler);
|
||||
Assert::assertMatchesRegularExpression('/A name is required\./', $formCrawler->text());
|
||||
Assert::assertMatchesRegularExpression('/A heading is required\./', $formCrawler->text());
|
||||
Assert::assertMatchesRegularExpression('/A message is required\./', $formCrawler->text());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Functional\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class PopupControllerTest extends MauticMysqlTestCase
|
||||
{
|
||||
/**
|
||||
* Smoke test to ensure the '/s/notifications' route loads.
|
||||
*/
|
||||
public function testIndexRouteSuccessfullyLoads(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/notification');
|
||||
$response = $this->client->getResponse();
|
||||
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,617 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Functional\EventListener;
|
||||
|
||||
use GuzzleHttp\Psr7\Response;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\CampaignBundle\Entity\Event as CampaignEvent;
|
||||
use Mautic\CampaignBundle\Entity\Lead as CampaignLead;
|
||||
use Mautic\CampaignBundle\Entity\LeadEventLog;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\NotificationBundle\Api\AbstractNotificationApi;
|
||||
use Mautic\NotificationBundle\Entity\Notification;
|
||||
use Mautic\NotificationBundle\EventListener\CampaignSubscriber;
|
||||
use Mautic\NotificationBundle\Tests\NotificationTrait;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Psr\Http\Message\RequestInterface;
|
||||
|
||||
class CampaignSubscriberTest extends MauticMysqlTestCase
|
||||
{
|
||||
use NotificationTrait;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const REST_API_ID = 'restApiID';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const API_ID = 'apiID';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private const ONESIGNAL_API_BASE_URL = 'https://onesignal.com/api/v1/notifications';
|
||||
|
||||
public function testLeadNotContactable(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['web-2']);
|
||||
$leadThree = $this->createLeadInCampaign($campaign, ['web-3a', 'web-3b']);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->createDoNotContact($leadOne, $notification);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['web-2', 'web-3a', 'web-3b'], $notification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogFailed($event, $leadOne, 'Contact is not contactable on the Web Notification channel.');
|
||||
$this->assertEventLogPassed($event, $leadTwo);
|
||||
$this->assertEventLogPassed($event, $leadThree);
|
||||
}
|
||||
|
||||
public function testNotificationMissing(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['web-2']);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
$event->setProperties([]);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$reason = 'The specified Web Notification entity does not exist.';
|
||||
$this->assertEventLogFailed($event, $leadOne, $reason);
|
||||
$this->assertEventLogFailed($event, $leadTwo, $reason);
|
||||
}
|
||||
|
||||
public function testNotificationUnpublished(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$notification->setIsPublished(false);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['web-2']);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$reason = 'The specified Web Notification is unpublished.';
|
||||
$this->assertEventLogFailed($event, $leadOne, $reason);
|
||||
$this->assertEventLogFailed($event, $leadTwo, $reason);
|
||||
}
|
||||
|
||||
public function testNotificationWithEmptyPushIds(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, []);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, []);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$reason = 'The contact has not subscribed to the Web Notification channel.';
|
||||
$this->assertEventLogFailed($event, $leadOne, $reason);
|
||||
$this->assertEventLogFailed($event, $leadTwo, $reason);
|
||||
}
|
||||
|
||||
public function testWebNotificationsAreSent(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['mobile-1'], true);
|
||||
$leadThree = $this->createLeadInCampaign($campaign, ['web-2a', 'web-2b']);
|
||||
$leadFour = $this->createLeadInCampaign($campaign, ['web-3']);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['web-1', 'web-2a', 'web-2b', 'web-3'], $notification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogPassed($event, $leadOne);
|
||||
$this->assertEventLogFailed($event, $leadTwo, 'The contact has not subscribed to the Web Notification channel.');
|
||||
$this->assertEventLogPassed($event, $leadThree);
|
||||
$this->assertEventLogPassed($event, $leadFour);
|
||||
}
|
||||
|
||||
public function testMobileNotificationsAreSent(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$notification->setMobile(true);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['mobile-1'], true);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadThree = $this->createLeadInCampaign($campaign, ['mobile-2a', 'mobile-2b'], true);
|
||||
$leadFour = $this->createLeadInCampaign($campaign, ['mobile-3a', 'mobile-3b'], true);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_mobile_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(
|
||||
['mobile-1', 'mobile-2a', 'mobile-2b', 'mobile-3a', 'mobile-3b'],
|
||||
$notification
|
||||
),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogPassed($event, $leadOne);
|
||||
$this->assertEventLogFailed($event, $leadTwo, 'The contact has not subscribed to the Web Notification channel.');
|
||||
$this->assertEventLogPassed($event, $leadThree);
|
||||
$this->assertEventLogPassed($event, $leadFour);
|
||||
}
|
||||
|
||||
public function testWebAndMobileNotificationsAreSent(): void
|
||||
{
|
||||
$webNotification = $this->createNotification($this->em);
|
||||
$webNotification->setHeading('Web heading 1');
|
||||
$webNotification->setMessage('Web message 1');
|
||||
|
||||
$mobileNotification = $this->createNotification($this->em);
|
||||
$mobileNotification->setHeading('Mobile heading 1');
|
||||
$mobileNotification->setMessage('Mobile message 1');
|
||||
$mobileNotification->setMobile(true);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['mobile-1'], true);
|
||||
$leadThree = $this->createLeadInCampaign($campaign, ['mobile-2a', 'mobile-2b'], true);
|
||||
$leadFour = $this->createLeadInCampaign($campaign, ['web-2a', 'web-2b']);
|
||||
$webEvent = $this->createCampaignEvent($campaign, $webNotification, 'notification.send_notification');
|
||||
$mobileEvent = $this->createCampaignEvent($campaign, $mobileNotification, 'notification.send_mobile_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['web-1', 'web-2a', 'web-2b'], $webNotification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['mobile-1', 'mobile-2a', 'mobile-2b'], $mobileNotification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL,
|
||||
500,
|
||||
'Internal server error'
|
||||
));
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$reason = 'Internal server error (500)';
|
||||
$this->assertEventLogPassed($webEvent, $leadOne);
|
||||
$this->assertEventLogFailed($mobileEvent, $leadTwo, $reason, true);
|
||||
$this->assertEventLogFailed($mobileEvent, $leadThree, $reason, true);
|
||||
$this->assertEventLogPassed($webEvent, $leadFour);
|
||||
}
|
||||
|
||||
public function testNotificationsWithToken(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$notification->setMessage('Message {contactfield=email}');
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadOne->setEmail('one@domain.tld');
|
||||
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['web-2a', 'web-2b']);
|
||||
$leadTwo->setEmail('two@domain.tld');
|
||||
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
[
|
||||
'include_player_ids' => ['web-1'],
|
||||
'contents' => ['en' => 'Message '.$leadOne->getEmail()],
|
||||
'headings' => ['en' => $notification->getHeading()],
|
||||
'app_id' => self::API_ID,
|
||||
],
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL,
|
||||
400,
|
||||
'Bad Request'
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
[
|
||||
'include_player_ids' => ['web-2a', 'web-2b'],
|
||||
'contents' => ['en' => 'Message '.$leadTwo->getEmail()],
|
||||
'headings' => ['en' => $notification->getHeading()],
|
||||
'app_id' => self::API_ID,
|
||||
],
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL,
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogFailed($event, $leadOne, 'Bad Request (400)', true);
|
||||
$this->assertEventLogPassed($event, $leadTwo);
|
||||
}
|
||||
|
||||
public function testWebNotificationsWithUrlAndButtons(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$notification->setUrl('https://some-url.tld');
|
||||
$notification->setButton('Some button');
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, []);
|
||||
$leadThree = $this->createLeadInCampaign($campaign, ['web-2']);
|
||||
$leadFour = $this->createLeadInCampaign($campaign, ['web-3a', 'web-3b']);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->createDoNotContact($leadOne, $notification);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$urlThree = $this->convertToTrackedUrl($notification, $leadThree);
|
||||
$urlFour = $this->convertToTrackedUrl($notification, $leadFour);
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
[
|
||||
'include_player_ids' => ['web-2'],
|
||||
'contents' => ['en' => $notification->getMessage()],
|
||||
'headings' => ['en' => $notification->getHeading()],
|
||||
'url' => $urlThree,
|
||||
'web_buttons' => [
|
||||
[
|
||||
'id' => $notification->getHeading(),
|
||||
'text' => $notification->getButton(),
|
||||
'url' => $urlThree,
|
||||
],
|
||||
],
|
||||
'app_id' => self::API_ID,
|
||||
],
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
[
|
||||
'include_player_ids' => ['web-3a', 'web-3b'],
|
||||
'contents' => ['en' => $notification->getMessage()],
|
||||
'headings' => ['en' => $notification->getHeading()],
|
||||
'url' => $urlFour,
|
||||
'web_buttons' => [
|
||||
[
|
||||
'id' => $notification->getHeading(),
|
||||
'text' => $notification->getButton(),
|
||||
'url' => $urlFour,
|
||||
],
|
||||
],
|
||||
'app_id' => self::API_ID,
|
||||
],
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogFailed($event, $leadOne, 'Contact is not contactable on the Web Notification channel.');
|
||||
$this->assertEventLogFailed($event, $leadTwo, 'The contact has not subscribed to the Web Notification channel.');
|
||||
$this->assertEventLogPassed($event, $leadThree);
|
||||
$this->assertEventLogPassed($event, $leadFour);
|
||||
}
|
||||
|
||||
public function testMobileNotificationsWithButtonsAndSettings(): void
|
||||
{
|
||||
$notification = $this->createNotification($this->em);
|
||||
$notification->setMobile(true);
|
||||
$notification->setButton('Some button');
|
||||
$notification->setMobileSettings([
|
||||
'ios_subtitle' => 'iOS Subtitle',
|
||||
'android_led_color' => 'FF00DD',
|
||||
]);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['mobile-1'], true);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['mobile-2a', 'mobile-2b'], true);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_mobile_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
[
|
||||
'include_player_ids' => ['mobile-1', 'mobile-2a', 'mobile-2b'],
|
||||
'contents' => ['en' => $notification->getMessage()],
|
||||
'headings' => ['en' => $notification->getHeading()],
|
||||
'subtitle' => ['en' => $notification->getMobileSettings()['ios_subtitle']],
|
||||
'android_led_color' => 'FF'.$notification->getMobileSettings()['android_led_color'],
|
||||
'buttons' => [
|
||||
[
|
||||
'id' => $notification->getHeading(),
|
||||
'text' => $notification->getButton(),
|
||||
],
|
||||
],
|
||||
'app_id' => self::API_ID,
|
||||
],
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogPassed($event, $leadOne);
|
||||
$this->assertEventLogPassed($event, $leadTwo);
|
||||
}
|
||||
|
||||
public function testNotificationsSentInBatches(): void
|
||||
{
|
||||
$subscriber = new class(static::getContainer()->get('mautic.helper.integration'), static::getContainer()->get('mautic.notification.model.notification'), static::getContainer()->get('mautic.notification.api'), static::getContainer()->get('event_dispatcher'), static::getContainer()->get('mautic.lead.model.dnc'), static::getContainer()->get('translator')) extends CampaignSubscriber {
|
||||
protected const MAX_PLAYER_IDS_PER_REQUEST = 2;
|
||||
};
|
||||
static::getContainer()->set('mautic.notification.campaignbundle.subscriber', $subscriber);
|
||||
|
||||
$notification = $this->createNotification($this->em);
|
||||
$this->em->flush();
|
||||
|
||||
$campaign = $this->createCampaign($this->em);
|
||||
$leadOne = $this->createLeadInCampaign($campaign, ['web-1']);
|
||||
$leadTwo = $this->createLeadInCampaign($campaign, ['mobile-1'], true);
|
||||
$leadThree = $this->createLeadInCampaign($campaign, ['web-2a', 'web-2b']);
|
||||
$leadFour = $this->createLeadInCampaign($campaign, ['web-3']);
|
||||
$leadFive = $this->createLeadInCampaign($campaign, ['web-4']);
|
||||
$event = $this->createCampaignEvent($campaign, $notification, 'notification.send_notification');
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['web-1', 'web-2a'], $notification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['web-2b', 'web-3'], $notification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->responseDataAssertion(
|
||||
$this->getExpectedResponsePushIds(['web-4'], $notification),
|
||||
'POST',
|
||||
self::ONESIGNAL_API_BASE_URL
|
||||
));
|
||||
|
||||
$this->transportMock->append($this->noMoreRequestAssertion());
|
||||
|
||||
$this->triggerCampaigns();
|
||||
|
||||
$this->assertEventLogPassed($event, $leadOne);
|
||||
$this->assertEventLogFailed($event, $leadTwo, 'The contact has not subscribed to the Web Notification channel.');
|
||||
$this->assertEventLogPassed($event, $leadThree);
|
||||
$this->assertEventLogPassed($event, $leadFour);
|
||||
$this->assertEventLogPassed($event, $leadFive);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $pushIds
|
||||
*/
|
||||
private function createLeadInCampaign(Campaign $campaign, array $pushIds, bool $mobile = false): Lead
|
||||
{
|
||||
$lead = new Lead();
|
||||
|
||||
foreach ($pushIds as $pushId) {
|
||||
$lead->addPushIDEntry($pushId, true, $mobile);
|
||||
}
|
||||
|
||||
$this->em->persist($lead);
|
||||
|
||||
$campaignLead = new CampaignLead();
|
||||
$campaignLead->setCampaign($campaign);
|
||||
$campaignLead->setLead($lead);
|
||||
$campaignLead->setDateAdded(new \DateTime());
|
||||
$this->em->persist($campaignLead);
|
||||
|
||||
return $lead;
|
||||
}
|
||||
|
||||
private function createCampaignEvent(Campaign $campaign, Notification $notification, string $type): CampaignEvent
|
||||
{
|
||||
$campaignEvent = new CampaignEvent();
|
||||
$campaignEvent->setCampaign($campaign);
|
||||
$campaignEvent->setName('Send notification');
|
||||
$campaignEvent->setType($type);
|
||||
$campaignEvent->setEventType('action');
|
||||
$campaignEvent->setProperties(['notification' => $notification->getId()]);
|
||||
$this->em->persist($campaignEvent);
|
||||
|
||||
return $campaignEvent;
|
||||
}
|
||||
|
||||
private function triggerCampaigns(): void
|
||||
{
|
||||
$this->testSymfonyCommand('mautic:campaigns:trigger');
|
||||
$this->em->clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param mixed[] $expectedData
|
||||
*/
|
||||
private function responseDataAssertion(
|
||||
array $expectedData,
|
||||
string $expectedMethod = 'GET',
|
||||
string $expectedUri = '',
|
||||
int $status = 200,
|
||||
?string $body = null,
|
||||
): callable {
|
||||
return static function (RequestInterface $request) use ($expectedData, $expectedMethod, $expectedUri, $status, $body) {
|
||||
Assert::assertSame($expectedMethod, $request->getMethod());
|
||||
Assert::assertSame($expectedUri, $request->getUri()->__toString());
|
||||
Assert::assertSame(json_encode($expectedData), $request->getBody()->getContents());
|
||||
$headers = $request->getHeaders();
|
||||
unset($headers['Content-Length']);
|
||||
Assert::assertSame([
|
||||
'User-Agent' => ['GuzzleHttp/7'],
|
||||
'Host' => ['onesignal.com'],
|
||||
'Authorization' => ['Basic '.self::REST_API_ID],
|
||||
'Content-Type' => ['application/json'],
|
||||
], $headers);
|
||||
|
||||
return new Response($status, [], $body);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string> $pushIds
|
||||
*
|
||||
* @return array<mixed>
|
||||
*/
|
||||
private function getExpectedResponsePushIds(array $pushIds, Notification $notification): array
|
||||
{
|
||||
return array_merge(
|
||||
['include_player_ids' => $pushIds],
|
||||
[
|
||||
'contents' => ['en' => $notification->getMessage()],
|
||||
'headings' => ['en' => $notification->getHeading()],
|
||||
'app_id' => self::API_ID,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
private function noMoreRequestAssertion(): callable
|
||||
{
|
||||
return function () {
|
||||
$this->fail('No other request was expected');
|
||||
};
|
||||
}
|
||||
|
||||
private function convertToTrackedUrl(Notification $notification, Lead $leadOne): string
|
||||
{
|
||||
/** @var AbstractNotificationApi $api */
|
||||
$api = static::getContainer()->get('mautic.notification.api');
|
||||
$clickThrough = [
|
||||
'notification' => $notification->getId(),
|
||||
'lead' => $leadOne->getId(),
|
||||
];
|
||||
|
||||
return $api->convertToTrackedUrl($notification->getUrl(), $clickThrough, $notification);
|
||||
}
|
||||
|
||||
private function assertEventLogPassed(CampaignEvent $event, Lead $leadOne): void
|
||||
{
|
||||
$log = $this->findEventLog($event, $leadOne);
|
||||
Assert::assertFalse($log->getIsScheduled());
|
||||
|
||||
$metadata = $log->getMetadata();
|
||||
Assert::assertIsArray($metadata);
|
||||
Assert::assertArrayHasKey('status', $metadata);
|
||||
Assert::assertSame('mautic.notification.timeline.status.delivered', $metadata['status']);
|
||||
}
|
||||
|
||||
private function assertEventLogFailed(CampaignEvent $event, Lead $leadOne, ?string $reason, bool $isScheduled = false): void
|
||||
{
|
||||
$log = $this->findEventLog($event, $leadOne);
|
||||
Assert::assertSame($isScheduled, $log->getIsScheduled());
|
||||
|
||||
$metadata = $log->getMetadata();
|
||||
Assert::assertIsArray($metadata);
|
||||
Assert::assertArrayHasKey('failed', $metadata);
|
||||
Assert::assertSame(1, $metadata['failed']);
|
||||
Assert::assertArrayHasKey('reason', $metadata);
|
||||
Assert::assertSame($reason, $metadata['reason']);
|
||||
}
|
||||
|
||||
private function findEventLog(CampaignEvent $event, Lead $leadOne): LeadEventLog
|
||||
{
|
||||
$log = $this->em->getRepository(LeadEventLog::class)->findOneBy([
|
||||
'event' => $event->getId(),
|
||||
'lead' => $leadOne,
|
||||
'rotation' => 1,
|
||||
]);
|
||||
Assert::assertNotNull($log);
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function createDoNotContact(Lead $lead, Notification $notification): DoNotContact
|
||||
{
|
||||
$doNotContact = new DoNotContact();
|
||||
$doNotContact->setLead($lead);
|
||||
$doNotContact->setChannel('notification');
|
||||
$doNotContact->setChannelId($notification->getId());
|
||||
$doNotContact->setReason(DoNotContact::UNSUBSCRIBED);
|
||||
$doNotContact->setDateAdded(new \DateTime());
|
||||
$this->em->persist($doNotContact);
|
||||
|
||||
return $doNotContact;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use GuzzleHttp\Handler\MockHandler;
|
||||
use Mautic\CampaignBundle\Entity\Campaign;
|
||||
use Mautic\NotificationBundle\Entity\Notification;
|
||||
use Mautic\PluginBundle\Integration\AbstractIntegration;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
trait NotificationTrait
|
||||
{
|
||||
private MockHandler $transportMock;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->transportMock = $this->getMockHandler(static::getContainer());
|
||||
$this->setupIntegration(static::getContainer(), $this->em, self::API_ID, self::REST_API_ID);
|
||||
}
|
||||
|
||||
private function getMockHandler(ContainerInterface $container): MockHandler
|
||||
{
|
||||
return $container->get(MockHandler::class);
|
||||
}
|
||||
|
||||
private function createNotification(EntityManagerInterface $em): Notification
|
||||
{
|
||||
$notification = new Notification();
|
||||
$notification->setName('Name 1');
|
||||
$notification->setHeading('Heading 1');
|
||||
$notification->setMessage('Message 1');
|
||||
$em->persist($notification);
|
||||
|
||||
return $notification;
|
||||
}
|
||||
|
||||
private function createCampaign(EntityManagerInterface $em): Campaign
|
||||
{
|
||||
$campaign = new Campaign();
|
||||
$campaign->setName('Notification');
|
||||
$em->persist($campaign);
|
||||
|
||||
return $campaign;
|
||||
}
|
||||
|
||||
private function setupIntegration(ContainerInterface $container, EntityManagerInterface $em, string $apiId, string $restApiId): void
|
||||
{
|
||||
/** @var AbstractIntegration $integration */
|
||||
$integration = $container->get('mautic.helper.integration')
|
||||
->getIntegrationObject('OneSignal');
|
||||
$integrationSettings = $integration->getIntegrationSettings();
|
||||
$integrationSettings->setIsPublished(true);
|
||||
$integration->encryptAndSetApiKeys([
|
||||
'app_id' => $apiId,
|
||||
'rest_api_key' => $restApiId,
|
||||
], $integrationSettings);
|
||||
$em->persist($integrationSettings);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\NotificationBundle\Tests\Unit\Api;
|
||||
|
||||
use Mautic\NotificationBundle\Api\OneSignalApi;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class OneSignalApiTest extends TestCase
|
||||
{
|
||||
public function testAddMobileData(): void
|
||||
{
|
||||
$mockOneSignalApi = $this->createMock(OneSignalApi::class);
|
||||
|
||||
$controllerReflection = (new \ReflectionClass(OneSignalApi::class));
|
||||
$method = $controllerReflection->getMethod('addMobileData');
|
||||
$method->setAccessible(true);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_subtitle' => 'test']]);
|
||||
$this->assertEquals(['subtitle' => ['en' => 'test']], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_sound' => 'test']]);
|
||||
$this->assertEquals(['ios_sound' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_sound' => '']]);
|
||||
$this->assertEquals(['ios_sound' => 'default'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_badges' => 'test']]);
|
||||
$this->assertEquals(['ios_badgeType' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_badgeCount' => '5']]);
|
||||
$this->assertEquals(['ios_badgeCount' => 5], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_contentAvailable' => true]]);
|
||||
$this->assertEquals(['content_available' => true], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['ios_mutableContent' => true]]);
|
||||
$this->assertEquals(['mutable_content' => true], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_sound' => 'test']]);
|
||||
$this->assertEquals(['android_sound' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_small_icon' => 'test']]);
|
||||
$this->assertEquals(['small_icon' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_large_icon' => 'test']]);
|
||||
$this->assertEquals(['large_icon' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_big_picture' => 'test']]);
|
||||
$this->assertEquals(['big_picture' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_led_color' => 'test']]);
|
||||
$this->assertEquals(['android_led_color' => 'FFTEST'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_accent_color' => 'test']]);
|
||||
$this->assertEquals(['android_accent_color' => 'FFTEST'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_group_key' => 'test']]);
|
||||
$this->assertEquals(['android_group' => 'test'], $data);
|
||||
|
||||
$data = [];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, ['android_lockscreen_visibility' => 1]]);
|
||||
$this->assertEquals(['android_visibility' => 1], $data);
|
||||
|
||||
$data = [];
|
||||
$mobileConfig = ['additional_data' => ['list' => [
|
||||
['label' => 'a', 'value' => 1],
|
||||
['label' => 'b', 'value' => 2],
|
||||
],
|
||||
],
|
||||
];
|
||||
$method->invokeArgs($mockOneSignalApi, [&$data, $mobileConfig]);
|
||||
$this->assertEquals(['data' => ['a' => 1, 'b' => 2]], $data);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user