Initial commit: CloudOps infrastructure platform
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Asset;
|
||||
|
||||
use Doctrine\ORM\ORMException;
|
||||
use Doctrine\Persistence\Mapping\MappingException;
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\CoreBundle\Helper\CsvHelper;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
|
||||
abstract class AbstractAssetTestCase extends MauticMysqlTestCase
|
||||
{
|
||||
protected Asset $asset;
|
||||
|
||||
protected string $expectedMimeType;
|
||||
|
||||
protected string $expectedContentDisposition;
|
||||
|
||||
protected string $expectedPngContent;
|
||||
|
||||
protected string $csvPath;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->generateCsv();
|
||||
|
||||
$assetData = [
|
||||
'title' => 'Asset controller test. Preview action',
|
||||
'alias' => 'Test',
|
||||
'createdAt' => new \DateTime('2021-05-05 22:30:00'),
|
||||
'updatedAt' => new \DateTime('2022-05-05 22:30:00'),
|
||||
'createdBy' => 'User',
|
||||
'storage' => 'local',
|
||||
'path' => basename($this->csvPath),
|
||||
'extension' => 'png',
|
||||
];
|
||||
$this->asset = $this->createAsset($assetData);
|
||||
|
||||
$this->expectedMimeType = 'text/plain; charset=UTF-8';
|
||||
$this->expectedContentDisposition = 'attachment;filename="';
|
||||
$this->expectedPngContent = file_get_contents($this->csvPath);
|
||||
}
|
||||
|
||||
protected function beforeTearDown(): void
|
||||
{
|
||||
if (file_exists($this->csvPath)) {
|
||||
unlink($this->csvPath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an asset entity in the DB.
|
||||
*
|
||||
* @param array<string, string|mixed> $assetData
|
||||
*
|
||||
* @throws ORMException
|
||||
* @throws MappingException
|
||||
*/
|
||||
protected function createAsset(array $assetData): Asset
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle($assetData['title']);
|
||||
$asset->setAlias($assetData['alias']);
|
||||
$asset->setDateAdded($assetData['createdAt'] ?? new \DateTime());
|
||||
$asset->setDateModified($assetData['updatedAt'] ?? new \DateTime());
|
||||
$asset->setCreatedByUser($assetData['createdBy'] ?? 'User');
|
||||
$asset->setStorageLocation($assetData['storage'] ?? 'local');
|
||||
$asset->setPath($assetData['path'] ?? '');
|
||||
$asset->setExtension($assetData['extension'] ?? '');
|
||||
$asset->setSize($this->csvPath ? filesize($this->csvPath) : 0);
|
||||
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->detach($asset);
|
||||
|
||||
return $asset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the csv asset and return the path of the asset.
|
||||
*/
|
||||
protected function generateCsv(): void
|
||||
{
|
||||
$uploadDir = static::getContainer()->get('mautic.helper.core_parameters')->get('upload_dir') ?? sys_get_temp_dir();
|
||||
$tmpFile = tempnam($uploadDir, 'mautic_asset_test_');
|
||||
$file = fopen($tmpFile, 'w');
|
||||
|
||||
$initialList = [
|
||||
['email', 'firstname', 'lastname'],
|
||||
['john.doe@his-site.com.email', 'John', 'Doe'],
|
||||
['john.smith@his-site.com.email', 'John', 'Smith'],
|
||||
['jim.doe@his-site.com.email', 'Jim', 'Doe'],
|
||||
[''],
|
||||
['jim.smith@his-site.com.email', 'Jim', 'Smith'],
|
||||
];
|
||||
|
||||
foreach ($initialList as $line) {
|
||||
CsvHelper::putCsv($file, $line);
|
||||
}
|
||||
|
||||
fclose($file);
|
||||
|
||||
$this->csvPath = $tmpFile;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller\Api;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
|
||||
class AssetApiControllerFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testCreateNewRemoteAsset(): void
|
||||
{
|
||||
$payload = [
|
||||
'file' => 'https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf',
|
||||
'storageLocation' => 'remote',
|
||||
'title' => 'title',
|
||||
];
|
||||
$this->client->request('POST', 'api/assets/new', $payload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertResponseStatusCodeSame(201, $clientResponse->getContent());
|
||||
$response = json_decode($clientResponse->getContent(), true);
|
||||
$this->assertEquals($payload['title'], $response['asset']['title']);
|
||||
$this->assertEquals($payload['storageLocation'], $response['asset']['storageLocation']);
|
||||
$this->assertStringContainsString('application/pdf', $response['asset']['mime']);
|
||||
$this->assertStringContainsString('pdf', $response['asset']['extension']);
|
||||
$this->assertNotNull($response['asset']['size']);
|
||||
}
|
||||
|
||||
public function testCreateNewRemoteAssetWithVulnerableFile(): void
|
||||
{
|
||||
$payload = [
|
||||
'file' => 'file:///etc/passwd',
|
||||
'storageLocation' => 'remote',
|
||||
'title' => 'title',
|
||||
];
|
||||
$this->client->request('POST', 'api/assets/new', $payload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertResponseStatusCodeSame(400, $clientResponse->getContent());
|
||||
$this->assertEquals('{"errors":[{"code":400,"message":"remotePath: The remote should be a valid URL.","details":{"remotePath":["The remote should be a valid URL."]}}]}', $clientResponse->getContent());
|
||||
}
|
||||
|
||||
public function testCreateNewLocalAsset(): void
|
||||
{
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
file_put_contents($assetsPath.'/file.txt', 'test');
|
||||
|
||||
$payload = [
|
||||
'file' => 'file.txt',
|
||||
'storageLocation' => 'local',
|
||||
'title' => 'title',
|
||||
];
|
||||
$this->client->request('POST', 'api/assets/new', $payload);
|
||||
$clientResponse = $this->client->getResponse();
|
||||
$this->assertResponseStatusCodeSame(201, $clientResponse->getContent());
|
||||
$response = json_decode($clientResponse->getContent(), true);
|
||||
$this->assertEquals($payload['title'], $response['asset']['title']);
|
||||
$this->assertEquals($payload['storageLocation'], $response['asset']['storageLocation']);
|
||||
$this->assertStringContainsString('text/plain', $response['asset']['mime']);
|
||||
$this->assertNotNull($response['asset']['size']);
|
||||
$this->assertStringContainsString('txt', $response['asset']['extension']);
|
||||
unlink($assetsPath.'/file.txt');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,388 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\AssetBundle\Tests\Asset\AbstractAssetTestCase;
|
||||
use Mautic\CoreBundle\Tests\Traits\ControllerTrait;
|
||||
use Mautic\PageBundle\Tests\Controller\PageControllerTest;
|
||||
use Mautic\ProjectBundle\Entity\Project;
|
||||
use Mautic\UserBundle\Entity\Permission;
|
||||
use Mautic\UserBundle\Entity\User;
|
||||
use Mautic\UserBundle\Model\RoleModel;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class AssetControllerFunctionalTest extends AbstractAssetTestCase
|
||||
{
|
||||
use ControllerTrait;
|
||||
|
||||
private const SALES_USER = 'sales';
|
||||
private const ADMIN_USER = 'admin';
|
||||
|
||||
/**
|
||||
* Index action should return status code 200.
|
||||
*/
|
||||
public function testIndexAction(): void
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('test');
|
||||
$asset->setAlias('test');
|
||||
$asset->setDateAdded(new \DateTime('2020-02-07 20:29:02'));
|
||||
$asset->setDateModified(new \DateTime('2020-03-21 20:29:02'));
|
||||
$asset->setCreatedByUser('Test User');
|
||||
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->detach($asset);
|
||||
|
||||
$urlAlias = 'assets';
|
||||
$routeAlias = 'asset';
|
||||
$column = 'dateModified';
|
||||
$column2 = 'title';
|
||||
$tableAlias = 'a.';
|
||||
|
||||
$this->getControllerColumnTests($urlAlias, $routeAlias, $column, $tableAlias, $column2);
|
||||
}
|
||||
|
||||
public function testAssetSizes(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/ajax?action=email:getAttachmentsSize&assets%5B%5D='.$this->asset->getId());
|
||||
$this->assertResponseIsSuccessful();
|
||||
Assert::assertSame('{"size":"178 bytes"}', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview action should return the file content.
|
||||
*/
|
||||
public function testPreviewActionStreamByDefault(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/assets/preview/'.$this->asset->getId());
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
$this->assertSame($this->expectedMimeType, $response->headers->get('Content-Type'));
|
||||
$this->assertNotSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview action should return the file content.
|
||||
*/
|
||||
public function testPreviewActionStreamIsZero(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/assets/preview/'.$this->asset->getId().'?stream=0&download=1');
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertSame(Response::HTTP_OK, $response->getStatusCode());
|
||||
$this->assertSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview action should return the html code.
|
||||
*/
|
||||
public function testPreviewActionStreamDownloadAreZero(): void
|
||||
{
|
||||
$this->client->request('GET', '/s/assets/preview/'.$this->asset->getId().'?stream=0&download=0');
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertSame(Response::HTTP_OK, $response->getStatusCode(), $content);
|
||||
$this->assertNotEquals($this->expectedPngContent, $content);
|
||||
PageControllerTest::assertTrue($response->isOk());
|
||||
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias();
|
||||
PageControllerTest::assertStringContainsString(
|
||||
'/asset/'.$assetSlug,
|
||||
$content,
|
||||
'The return must contain the assert slug'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, string[]> $permission
|
||||
*/
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('getValuesProvider')]
|
||||
public function testEditWithPermissions(string $route, array $permission, int $expectedStatusCode, string $userCreatorUN): void
|
||||
{
|
||||
$userCreator = $this->getUser($userCreatorUN);
|
||||
$userEditor = $this->getUser(self::SALES_USER);
|
||||
$this->setPermission($userEditor, ['asset:assets' => $permission]);
|
||||
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('Asset A');
|
||||
$asset->setAlias('asset-a');
|
||||
$asset->setStorageLocation('local');
|
||||
$asset->setPath('broken-image.jpg');
|
||||
$asset->setExtension('jpg');
|
||||
$asset->setCreatedByUser($userCreator->getUserIdentifier());
|
||||
$asset->setCreatedBy($userCreator->getId());
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->logoutUser();
|
||||
|
||||
$this->loginUser($userEditor);
|
||||
|
||||
$this->client->request(Request::METHOD_GET, "/s/assets/{$route}/{$asset->getId()}");
|
||||
|
||||
Assert::assertSame($expectedStatusCode, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<string, mixed[]>
|
||||
*/
|
||||
public static function getValuesProvider(): \Generator
|
||||
{
|
||||
yield 'The sales user with edit own permission can edits its own asset' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['editown'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::SALES_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with edit own permission cannot edit asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['editown'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with edit other permission can edit asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['editown', 'editother'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view own permission cannot edit or asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['viewown'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view other permission cannot edit asset created by admin' => [
|
||||
'route' => 'edit',
|
||||
'permission' => ['viewown', 'viewother'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view own permission cannot view asset created by admin' => [
|
||||
'route' => 'view',
|
||||
'permission' => ['viewown'],
|
||||
'expectedStatusCode' => Response::HTTP_FORBIDDEN,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view others permission can view asset created by admin' => [
|
||||
'route' => 'view',
|
||||
'permission' => ['viewown', 'viewother'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::ADMIN_USER,
|
||||
];
|
||||
|
||||
yield 'The sales user with view own permission can view its own asset' => [
|
||||
'route' => 'view',
|
||||
'permission' => ['viewown'],
|
||||
'expectedStatusCode' => Response::HTTP_OK,
|
||||
'userCreatorUN' => self::SALES_USER,
|
||||
];
|
||||
}
|
||||
|
||||
public function testAssetUploadPathTraversal(): void
|
||||
{
|
||||
$client = $this->client;
|
||||
$container = $this->getContainer();
|
||||
|
||||
// Get CSRF token
|
||||
$csrfToken = $container->get('security.csrf.token_manager')->getToken('mautic_ajax_post')->getValue();
|
||||
|
||||
// Create a temporary file
|
||||
$tempFile = tempnam(sys_get_temp_dir(), 'test_');
|
||||
file_put_contents($tempFile, '111');
|
||||
|
||||
// Prepare the file for upload
|
||||
$uploadedFile = new UploadedFile(
|
||||
$tempFile,
|
||||
'test.txt',
|
||||
'text/plain',
|
||||
null,
|
||||
true
|
||||
);
|
||||
|
||||
$tmpDir = 'tmp_'.substr(md5(uniqid()), 0, 13);
|
||||
$client->request(
|
||||
'POST',
|
||||
'/s/_uploader/asset/upload',
|
||||
['tempId' => '../../'.$tmpDir],
|
||||
['file' => $uploadedFile],
|
||||
[
|
||||
'HTTP_X-Requested-With' => 'XMLHttpRequest',
|
||||
'HTTP_X-CSRF-Token' => $csrfToken,
|
||||
]
|
||||
);
|
||||
|
||||
$response = $client->getResponse();
|
||||
|
||||
// Assert response is successful
|
||||
$this->assertEquals(Response::HTTP_OK, $response->getStatusCode());
|
||||
|
||||
// Decode JSON response
|
||||
$responseData = json_decode($response->getContent(), true);
|
||||
|
||||
// Assert the response contains expected keys
|
||||
$this->assertArrayHasKey('tmpFileName', $responseData);
|
||||
|
||||
// Assert file was created in the correct directory
|
||||
$expectedDir = $container->getParameter('mautic.upload_dir').join('/', ['', 'tmp', $tmpDir]);
|
||||
$expectedFilePath = join('/', [$expectedDir, $responseData['tmpFileName']]);
|
||||
$this->assertFileExists($expectedFilePath);
|
||||
|
||||
// Clean up
|
||||
if (file_exists($expectedFilePath)) {
|
||||
unlink($expectedFilePath);
|
||||
}
|
||||
if (is_dir($expectedDir)) {
|
||||
rmdir($expectedDir);
|
||||
}
|
||||
if (file_exists($tempFile)) {
|
||||
unlink($tempFile);
|
||||
}
|
||||
}
|
||||
|
||||
private function getUser(string $username): User
|
||||
{
|
||||
$repository = $this->em->getRepository(User::class);
|
||||
|
||||
return $repository->findOneBy(['username' => $username]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, array<string, array<string>>> $permissions
|
||||
*/
|
||||
private function setPermission(User $user, array $permissions): void
|
||||
{
|
||||
$role = $user->getRole();
|
||||
|
||||
// Delete previous permissions
|
||||
$this->em->createQueryBuilder()
|
||||
->delete(Permission::class, 'p')
|
||||
->where('p.bundle = :bundle')
|
||||
->andWhere('p.role = :role_id')
|
||||
->setParameters(['bundle' => 'asset', 'role_id' => $role->getId()])
|
||||
->getQuery()
|
||||
->execute();
|
||||
|
||||
// Set new permissions
|
||||
$role->setIsAdmin(false);
|
||||
$roleModel = static::getContainer()->get('mautic.user.model.role');
|
||||
\assert($roleModel instanceof RoleModel);
|
||||
$roleModel->setRolePermissions($role, $permissions);
|
||||
$this->em->persist($role);
|
||||
$this->em->flush();
|
||||
}
|
||||
|
||||
public function testPostRequestWithWrongTempNameAndOriginalFileNameFileExtension(): void
|
||||
{
|
||||
$response = $this->client->request(
|
||||
Request::METHOD_GET,
|
||||
'/s/assets/new',
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$form = $response->filter('form[name="asset"]')->form();
|
||||
$data = $form->getPhpValues();
|
||||
$data['asset']['tempName'] = 'image2.php';
|
||||
$data['asset']['originalFileName'] = 'originalImage2.php';
|
||||
$data['asset']['storageLocation'] = 'local';
|
||||
$data['asset']['title'] = 'title';
|
||||
$data['asset']['description'] = 'description';
|
||||
$this->client->submit($form, $data);
|
||||
preg_match_all('/Upload failed as the file extension, php/', $this->client->getResponse()->getContent(), $matches);
|
||||
$this->assertCount(2, $matches[0]);
|
||||
$this->assertStringContainsString('Upload failed as the file extension, php', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testPostRequestWithWrongTempNameFileExtension(): void
|
||||
{
|
||||
$response = $this->client->request(
|
||||
Request::METHOD_GET,
|
||||
'/s/assets/new',
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$form = $response->filter('form[name="asset"]')->form();
|
||||
$data = $form->getPhpValues();
|
||||
$data['asset']['tempName'] = 'image2.php';
|
||||
$data['asset']['originalFileName'] = 'originalImage2.png';
|
||||
$data['asset']['storageLocation'] = 'local';
|
||||
$data['asset']['title'] = 'title';
|
||||
$data['asset']['description'] = 'description';
|
||||
$this->client->submit($form, $data);
|
||||
preg_match_all('/Upload failed as the file extension, php/', $this->client->getResponse()->getContent(), $matches);
|
||||
$this->assertCount(1, $matches[0]);
|
||||
$this->assertStringContainsString('Upload failed as the file extension, php', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testPostResquetSuccessWithCorrectFileExtension(): void
|
||||
{
|
||||
$response = $this->client->request(
|
||||
Request::METHOD_GET,
|
||||
'/s/assets/new',
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$form = $response->filter('form[name="asset"]')->form();
|
||||
$data = $form->getPhpValues();
|
||||
$data['asset']['tempName'] = 'image.png';
|
||||
$data['asset']['originalFileName'] = 'originalImage.png';
|
||||
$data['asset']['storageLocation'] = 'local';
|
||||
$data['asset']['title'] = 'title';
|
||||
$data['asset']['description'] = 'description';
|
||||
$this->client->submit($form, $data);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$this->assertStringNotContainsString('Upload failed as the file extension, php', $this->client->getResponse()->getContent());
|
||||
}
|
||||
|
||||
public function testAssetWithProject(): void
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('test');
|
||||
$asset->setAlias('test');
|
||||
$this->em->persist($asset);
|
||||
|
||||
$project = new Project();
|
||||
$project->setName('Test Project');
|
||||
$this->em->persist($project);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$crawler = $this->client->request('GET', '/s/assets/edit/'.$asset->getId());
|
||||
$form = $crawler->selectButton('Save')->form();
|
||||
$form['asset[projects]']->setValue((string) $project->getId());
|
||||
|
||||
$this->client->submit($form);
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
|
||||
$savedAsset = $this->em->find(Asset::class, $asset->getId());
|
||||
Assert::assertSame($project->getId(), $savedAsset->getProjects()->first()->getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
class AssetDetailFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testLeadViewPreventsXSS(): void
|
||||
{
|
||||
$title = 'aaa" onerror=alert(1) a="';
|
||||
$asset = new Asset();
|
||||
$asset->setTitle($title);
|
||||
$asset->setAlias('dummy-alias');
|
||||
$asset->setStorageLocation('local');
|
||||
$asset->setPath('broken-image.jpg');
|
||||
$asset->setExtension('jpg');
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
$this->em->detach($asset);
|
||||
|
||||
$crawler = $this->client->request('GET', sprintf('/s/assets/view/%d', $asset->getId()));
|
||||
$imageTag = $crawler->filter('.img-thumbnail');
|
||||
|
||||
$onError = $imageTag->attr('onerror');
|
||||
$altProp = $imageTag->attr('alt');
|
||||
|
||||
Assert::assertNull($onError);
|
||||
Assert::assertSame($title, $altProp);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
final class AssetDownloadFunctionalTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testDownloadOfNotFoundAsset(): void
|
||||
{
|
||||
$this->client->request(Request::METHOD_GET, '/s/logout');
|
||||
|
||||
// The 500 error happened only on the second request.
|
||||
// It happened only if the device was already tracked.
|
||||
$this->client->request(Request::METHOD_GET, '/asset/unicorn'); // returns 404 correctly
|
||||
$this->client->request(Request::METHOD_GET, '/asset/unicorn'); // returned 500 but it should return 404
|
||||
|
||||
Assert::assertSame(Response::HTTP_NOT_FOUND, $this->client->getResponse()->getStatusCode());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\ProjectBundle\Tests\Functional\AbstractProjectSearchTestCase;
|
||||
|
||||
final class AssetProjectSearchFunctionalTest extends AbstractProjectSearchTestCase
|
||||
{
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('searchDataProvider')]
|
||||
public function testProjectSearch(string $searchTerm, array $expectedEntities, array $unexpectedEntities): void
|
||||
{
|
||||
$projectOne = $this->createProject('Project One');
|
||||
$projectTwo = $this->createProject('Project Two');
|
||||
$projectThree = $this->createProject('Project Three');
|
||||
|
||||
$assetAlpha = $this->createAsset('Asset Alpha');
|
||||
$assetBeta = $this->createAsset('Asset Beta');
|
||||
$this->createAsset('Asset Gamma');
|
||||
$this->createAsset('Asset Delta');
|
||||
|
||||
$assetAlpha->addProject($projectOne);
|
||||
$assetAlpha->addProject($projectTwo);
|
||||
$assetBeta->addProject($projectTwo);
|
||||
$assetBeta->addProject($projectThree);
|
||||
|
||||
$this->em->flush();
|
||||
$this->em->clear();
|
||||
|
||||
$this->searchAndAssert($searchTerm, $expectedEntities, $unexpectedEntities, ['/api/assets', '/s/assets']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Generator<string, array{searchTerm: string, expectedEntities: array<string>, unexpectedEntities: array<string>}>
|
||||
*/
|
||||
public static function searchDataProvider(): \Generator
|
||||
{
|
||||
yield 'search by one project' => [
|
||||
'searchTerm' => 'project:"Project Two"',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by one project AND asset name' => [
|
||||
'searchTerm' => 'project:"Project Two" AND Beta',
|
||||
'expectedEntities' => ['Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by one project OR asset name' => [
|
||||
'searchTerm' => 'project:"Project Two" OR Gamma',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Beta', 'Asset Gamma'],
|
||||
'unexpectedEntities' => ['Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by NOT one project' => [
|
||||
'searchTerm' => '!project:"Project Two"',
|
||||
'expectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with AND' => [
|
||||
'searchTerm' => 'project:"Project Two" AND project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with NOT AND' => [
|
||||
'searchTerm' => '!project:"Project Two" AND !project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
'unexpectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with OR' => [
|
||||
'searchTerm' => 'project:"Project Two" OR project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Beta'],
|
||||
'unexpectedEntities' => ['Asset Gamma', 'Asset Delta'],
|
||||
];
|
||||
|
||||
yield 'search by two projects with NOT OR' => [
|
||||
'searchTerm' => '!project:"Project Two" OR !project:"Project Three"',
|
||||
'expectedEntities' => ['Asset Alpha', 'Asset Gamma', 'Asset Delta'],
|
||||
'unexpectedEntities' => ['Asset Beta'],
|
||||
];
|
||||
}
|
||||
|
||||
private function createAsset(string $name): Asset
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle($name);
|
||||
$asset->setAlias($name);
|
||||
$this->em->persist($asset);
|
||||
|
||||
return $asset;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Download;
|
||||
use Mautic\AssetBundle\Tests\Asset\AbstractAssetTestCase;
|
||||
|
||||
class PublicControllerFunctionalTest extends AbstractAssetTestCase
|
||||
{
|
||||
/**
|
||||
* Download action should return the file content.
|
||||
*/
|
||||
public function testDownloadActionStreamByDefault(): void
|
||||
{
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias();
|
||||
|
||||
$this->client->request('GET', '/asset/'.$assetSlug);
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->expectedMimeType, $response->headers->get('Content-Type'));
|
||||
$this->assertNotSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download action should return the file content.
|
||||
*/
|
||||
public function testDownloadActionStreamIsZero(): void
|
||||
{
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias();
|
||||
|
||||
$this->client->request('GET', '/asset/'.$assetSlug.'?stream=0');
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Download action with UTM should return the file content.
|
||||
*/
|
||||
public function testDownloadActionWithUTM(): void
|
||||
{
|
||||
$this->logoutUser();
|
||||
$assetSlug = $this->asset->getId().':'.$this->asset->getAlias().'?utm_source=test2&utm_medium=test3&utm_campaign=test6&utm_term=test4&utm_content=test5';
|
||||
|
||||
$this->client->request('GET', '/asset/'.$assetSlug);
|
||||
ob_start();
|
||||
$response = $this->client->getResponse();
|
||||
$response->sendContent();
|
||||
$content = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
$this->assertResponseIsSuccessful();
|
||||
$this->assertSame($this->expectedMimeType, $response->headers->get('Content-Type'));
|
||||
$this->assertNotSame($this->expectedContentDisposition.$this->asset->getOriginalFileName(), $response->headers->get('Content-Disposition'));
|
||||
$this->assertEquals($this->expectedPngContent, $content);
|
||||
|
||||
$downloadRepo = $this->em->getRepository(Download::class);
|
||||
|
||||
$download = $downloadRepo->findOneBy(['asset' => $this->asset]);
|
||||
\assert($download instanceof Download);
|
||||
$this->assertSame('test2', $download->getUtmSource());
|
||||
$this->assertSame('test3', $download->getUtmMedium());
|
||||
$this->assertSame('test4', $download->getUtmTerm());
|
||||
$this->assertSame('test5', $download->getUtmContent());
|
||||
$this->assertSame('test6', $download->getUtmCampaign());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Controller;
|
||||
|
||||
use Mautic\AssetBundle\Tests\Asset\AbstractAssetTestCase;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
|
||||
class UploadControllerFunctionalTest extends AbstractAssetTestCase
|
||||
{
|
||||
public function testUploadWithWrongMimetype(): void
|
||||
{
|
||||
// Create a php file with the content of phpinfo
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
|
||||
$fileName = 'image2.png';
|
||||
$filePath = $assetsPath.'/'.$fileName;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
copy('index.php', $filePath);
|
||||
|
||||
$binaryFile = new UploadedFile($filePath, $fileName, 'application/x-httpd-php', null, true);
|
||||
|
||||
$tmpId = 'tempId_'.time();
|
||||
// Upload the file
|
||||
$this->client->request(
|
||||
Request::METHOD_POST,
|
||||
'/s/_uploader/asset/upload',
|
||||
[
|
||||
'tempId' => $tmpId,
|
||||
],
|
||||
[
|
||||
'file' => $binaryFile,
|
||||
]
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertStringContainsString('Upload failed as the file mimetype', $response->getContent());
|
||||
$this->assertStringContainsString('text\/x-php is not allowed', $response->getContent());
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
public function testSuccessUploadWithPng(): void
|
||||
{
|
||||
// Create a temporary PNG file
|
||||
// Create a php file with the content of phpinfo
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
$assetsPathFrom = $this->client->getKernel()->getContainer()->getParameter('mautic.application_dir').'/app/assets/images/mautic_logo_db64.png';
|
||||
|
||||
$fileName = 'image3.png';
|
||||
$filePath = $assetsPath.'/'.$fileName;
|
||||
|
||||
copy($assetsPathFrom, $filePath);
|
||||
// Create an UploadedFile instance with the correct MIME type
|
||||
$uploadedFile = new UploadedFile($filePath, $fileName, 'image/png', null, true);
|
||||
|
||||
$tmpId = 'tempId_'.time();
|
||||
// Perform the request with the file
|
||||
$this->client->request(
|
||||
'POST',
|
||||
'/s/_uploader/asset/upload',
|
||||
['tempId' => $tmpId],
|
||||
['file' => $uploadedFile]
|
||||
);
|
||||
$this->assertResponseStatusCodeSame(Response::HTTP_OK);
|
||||
$this->assertStringContainsString('state":1', $this->client->getResponse()->getContent());
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
$data = json_decode($this->client->getResponse()->getContent(), true);
|
||||
unlink($assetsPath.'/tmp/'.$tmpId.'/'.$data['tmpFileName']);
|
||||
rmdir($assetsPath.'/tmp/'.$tmpId);
|
||||
}
|
||||
|
||||
public function testUploadWithWrongExtension(): void
|
||||
{
|
||||
// Create a php file with the content of phpinfo
|
||||
$assetsPath = $this->client->getKernel()->getContainer()->getParameter('mautic.upload_dir');
|
||||
$assetsPathFrom = $this->client->getKernel()->getContainer()->getParameter('mautic.application_dir').'/app/assets/images/mautic_logo_db64.png';
|
||||
|
||||
$fileName = 'image2.php';
|
||||
$filePath = $assetsPath.'/'.$fileName;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
|
||||
copy($assetsPathFrom, $filePath);
|
||||
|
||||
$binaryFile = new UploadedFile($filePath, $fileName, 'image/png', null, true);
|
||||
|
||||
$tmpId = 'tempId_'.time();
|
||||
// Upload the file
|
||||
$this->client->request(
|
||||
Request::METHOD_POST,
|
||||
'/s/_uploader/asset/upload',
|
||||
[
|
||||
'tempId' => $tmpId,
|
||||
],
|
||||
[
|
||||
'file' => $binaryFile,
|
||||
]
|
||||
);
|
||||
|
||||
$response = $this->client->getResponse();
|
||||
$this->assertStringContainsString('Upload failed as the file extension', $response->getContent());
|
||||
$this->assertStringContainsString('Upload failed as the file extension, php,', $response->getContent());
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\DataFixtures;
|
||||
|
||||
use Mautic\AssetBundle\DataFixtures\ORM\LoadAssetData;
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\CoreBundle\Test\MauticMysqlTestCase;
|
||||
|
||||
class LoadAssetDataTest extends MauticMysqlTestCase
|
||||
{
|
||||
public function testLoadFixtures(): void
|
||||
{
|
||||
$this->loadFixtures([LoadAssetData::class]);
|
||||
$asset = $this->em->getRepository(Asset::class)->findOneBy(
|
||||
['title' => '@TOCHANGE: Asset1 Title'],
|
||||
['id' => 'DESC']
|
||||
);
|
||||
self::assertInstanceOf(Asset::class, $asset);
|
||||
self::assertEquals('asset1', $asset->getAlias());
|
||||
self::assertEquals('@TOCHANGE: Asset1 Original File Name', $asset->getOriginalFileName());
|
||||
self::assertEquals('fdb8e28357b02d12d068de3e5661832e21bc08ec.doc', $asset->getPath());
|
||||
self::assertEquals(1, $asset->getDownloadCount());
|
||||
self::assertEquals(1, $asset->getUniqueDownloadCount());
|
||||
self::assertEquals(1, $asset->getRevision());
|
||||
self::assertEquals('en', $asset->getLanguage());
|
||||
}
|
||||
|
||||
public function testLoadFixturesOrder(): void
|
||||
{
|
||||
$loadAssetData = new LoadAssetData();
|
||||
self::assertEquals(10, $loadAssetData->getOrder());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Entity;
|
||||
|
||||
use Doctrine\DBAL\Query\QueryBuilder;
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\AssetBundle\Entity\AssetRepository;
|
||||
use Mautic\CoreBundle\Test\Doctrine\RepositoryConfiguratorTrait;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class AssetRepositoryTest extends TestCase
|
||||
{
|
||||
use RepositoryConfiguratorTrait;
|
||||
|
||||
private function getRepository(): AssetRepository
|
||||
{
|
||||
$repository = $this->configureRepository(Asset::class);
|
||||
$this->connection->method('createQueryBuilder')->willReturnCallback(fn () => new QueryBuilder($this->connection));
|
||||
|
||||
$translator = $this->createMock(TranslatorInterface::class);
|
||||
$translator->method('trans')->willReturnCallback(fn ($id) => match ($id) {
|
||||
'mautic.asset.asset.searchcommand.isexpired' => 'is:expired',
|
||||
'mautic.asset.asset.searchcommand.ispending' => 'is:pending',
|
||||
default => $id,
|
||||
});
|
||||
$repository->setTranslator($translator);
|
||||
|
||||
return $repository;
|
||||
}
|
||||
|
||||
#[\PHPUnit\Framework\Attributes\DataProvider('dataExpirationFilters')]
|
||||
public function testAddSearchCommandWhereClauseHandlesExpirationFilters(string $command, string $expected): void
|
||||
{
|
||||
$repository = $this->getRepository();
|
||||
$qb = $this->connection->createQueryBuilder();
|
||||
$filter = (object) ['command' => $command, 'string' => '', 'not' => false, 'strict' => false];
|
||||
|
||||
$method = new \ReflectionMethod(AssetRepository::class, 'addSearchCommandWhereClause');
|
||||
$method->setAccessible(true);
|
||||
|
||||
[$expr, $params] = $method->invoke($repository, $qb, $filter);
|
||||
|
||||
self::assertSame($expected, (string) $expr);
|
||||
self::assertSame(['par1' => true], $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return iterable<array{0: string, 1: string}>
|
||||
*/
|
||||
public static function dataExpirationFilters(): iterable
|
||||
{
|
||||
yield ['is:expired', "(a.isPublished = :par1 AND a.publishDown IS NOT NULL AND a.publishDown <> '' AND a.publishDown < CURRENT_TIMESTAMP())"];
|
||||
yield ['is:pending', "(a.isPublished = :par1 AND a.publishUp IS NOT NULL AND a.publishUp <> '' AND a.publishUp > CURRENT_TIMESTAMP())"];
|
||||
}
|
||||
|
||||
public function testGetSearchCommandsContainsExpirationFilters(): void
|
||||
{
|
||||
$repository = $this->getRepository();
|
||||
$commands = $repository->getSearchCommands();
|
||||
self::assertContains('mautic.asset.asset.searchcommand.isexpired', $commands);
|
||||
self::assertContains('mautic.asset.asset.searchcommand.ispending', $commands);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\EventListener;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Mautic\AssetBundle\Entity\DownloadRepository;
|
||||
use Mautic\AssetBundle\EventListener\DetermineWinnerSubscriber;
|
||||
use Mautic\CoreBundle\Event\DetermineWinnerEvent;
|
||||
use Mautic\PageBundle\Entity\Page;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class DetermineWinnerSubscriberTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
/**
|
||||
* @var MockObject|EntityManagerInterface
|
||||
*/
|
||||
private MockObject $em;
|
||||
|
||||
/**
|
||||
* @var MockObject|TranslatorInterface
|
||||
*/
|
||||
private MockObject $translator;
|
||||
|
||||
private DetermineWinnerSubscriber $subscriber;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->translator = $this->createMock(TranslatorInterface::class);
|
||||
$this->subscriber = new DetermineWinnerSubscriber($this->em, $this->translator);
|
||||
}
|
||||
|
||||
public function testOnDetermineDownloadRateWinner(): void
|
||||
{
|
||||
$parentMock = $this->createMock(Page::class);
|
||||
$childMock = $this->createMock(Page::class);
|
||||
$children = [2 => $childMock];
|
||||
$repoMock = $this->createMock(DownloadRepository::class);
|
||||
$parameters = ['parent' => $parentMock, 'children' => $children];
|
||||
$event = new DetermineWinnerEvent($parameters);
|
||||
$startDate = new \DateTime();
|
||||
|
||||
$transDownloads = 'downloads';
|
||||
$transHits = 'hits';
|
||||
|
||||
$counts = [
|
||||
1 => [
|
||||
'count' => 20,
|
||||
'id' => 1,
|
||||
'name' => 'Test 5',
|
||||
'total' => 100,
|
||||
],
|
||||
2 => [
|
||||
'count' => 25,
|
||||
'id' => 2,
|
||||
'name' => 'Test 6',
|
||||
'total' => 150,
|
||||
],
|
||||
];
|
||||
|
||||
$this->translator->method('trans')
|
||||
->willReturnOnConsecutiveCalls($transDownloads, $transHits);
|
||||
|
||||
$this->em->expects($this->once())
|
||||
->method('getRepository')
|
||||
->willReturn($repoMock);
|
||||
|
||||
$parentMock->expects($this->any())
|
||||
->method('isPublished')
|
||||
->willReturn(true);
|
||||
|
||||
$childMock->expects($this->any())
|
||||
->method('isPublished')
|
||||
->willReturn(true);
|
||||
|
||||
$parentMock->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(1);
|
||||
|
||||
$childMock->expects($this->any())
|
||||
->method('getId')
|
||||
->willReturn(2);
|
||||
|
||||
$parentMock->expects($this->once())
|
||||
->method('getVariantStartDate')
|
||||
->willReturn($startDate);
|
||||
|
||||
$repoMock->expects($this->once())
|
||||
->method('getDownloadCountsByPage')
|
||||
->with([1, 2], $startDate)
|
||||
->willReturn($counts);
|
||||
|
||||
$this->subscriber->onDetermineDownloadRateWinner($event);
|
||||
|
||||
$expectedData = [
|
||||
$transDownloads => [$counts[1]['count'], $counts[2]['count']],
|
||||
$transHits => [$counts[1]['total'], $counts[2]['total']],
|
||||
];
|
||||
|
||||
$abTestResults = $event->getAbTestResults();
|
||||
|
||||
$this->assertEquals($abTestResults['winners'], [1]);
|
||||
$this->assertEquals($abTestResults['support']['data'], $expectedData);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\EventListener;
|
||||
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\AssetBundle\Entity\Download;
|
||||
use Mautic\LeadBundle\Entity\DoNotContact;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\ReportBundle\Tests\Functional\AbstractReportSubscriberTestCase;
|
||||
|
||||
class ReportSubscriberFunctionalTest extends AbstractReportSubscriberTestCase
|
||||
{
|
||||
public function testAssetDownloadReportWithDncListColumn(): void
|
||||
{
|
||||
$leads[] = $this->createContact('test1@example.com');
|
||||
$leads[] = $this->createContact('test2@example.com');
|
||||
$leads[] = $this->createContact('test3@example.com');
|
||||
$this->em->flush();
|
||||
|
||||
$this->createDnc('email', $leads[0], DoNotContact::BOUNCED);
|
||||
$this->createDnc('email', $leads[1], DoNotContact::MANUAL);
|
||||
$this->createDnc('email', $leads[2], DoNotContact::UNSUBSCRIBED);
|
||||
$this->createDnc('sms', $leads[2], DoNotContact::MANUAL);
|
||||
$this->em->flush();
|
||||
|
||||
$asset = $this->createAsset();
|
||||
$this->emulateAssetDownload($asset, $leads[0]);
|
||||
$this->emulateAssetDownload($asset, $leads[1]);
|
||||
$this->emulateAssetDownload($asset, $leads[2]);
|
||||
|
||||
$report = $this->createReport(
|
||||
source: 'asset.downloads',
|
||||
columns: ['l.id', 'a.id', 'a.title', 'dnc_preferences'],
|
||||
filters: [
|
||||
[
|
||||
'column' => 'dnc_preferences',
|
||||
'glue' => 'and',
|
||||
'dynamic' => null,
|
||||
'condition' => 'in',
|
||||
'value' => [
|
||||
'email:'.DoNotContact::UNSUBSCRIBED,
|
||||
'email:'.DoNotContact::BOUNCED,
|
||||
],
|
||||
],
|
||||
],
|
||||
order: [['column' => 'l.id', 'direction' => 'ASC']]
|
||||
);
|
||||
|
||||
$expectedReport = [
|
||||
[(string) $leads[0]->getId(), (string) $asset->getId(), $asset->getTitle(), 'DNC Bounced: Email'],
|
||||
[(string) $leads[2]->getId(), (string) $asset->getId(), $asset->getTitle(), 'DNC Manually Unsubscribed: Text Message, DNC Unsubscribed: Email'],
|
||||
];
|
||||
$this->verifyReport($report->getId(), $expectedReport);
|
||||
$this->verifyApiReport($report->getId(), $expectedReport);
|
||||
}
|
||||
|
||||
private function createAsset(): Asset
|
||||
{
|
||||
$asset = new Asset();
|
||||
$asset->setTitle('test');
|
||||
$asset->setAlias('test');
|
||||
$asset->setDateAdded(new \DateTime('2020-02-07 20:29:02'));
|
||||
$asset->setDateModified(new \DateTime('2020-03-21 20:29:02'));
|
||||
$asset->setCreatedByUser('Test User');
|
||||
|
||||
$this->em->persist($asset);
|
||||
$this->em->flush();
|
||||
|
||||
return $asset;
|
||||
}
|
||||
|
||||
private function emulateAssetDownload(Asset $asset, Lead $contact): Download
|
||||
{
|
||||
$assetDownload = new Download();
|
||||
$assetDownload->setAsset($asset);
|
||||
$assetDownload->setLead($contact);
|
||||
$assetDownload->setDateDownload(new \DateTime());
|
||||
$assetDownload->setCode(200);
|
||||
$assetDownload->setTrackingId(random_int(1, 99999));
|
||||
$this->em->persist($assetDownload);
|
||||
$this->em->flush();
|
||||
|
||||
return $assetDownload;
|
||||
}
|
||||
|
||||
private function createContact(string $email): Lead
|
||||
{
|
||||
$contact = new Lead();
|
||||
$contact->setEmail($email);
|
||||
$this->em->persist($contact);
|
||||
|
||||
return $contact;
|
||||
}
|
||||
|
||||
public function createDnc(string $channel, Lead $contact, int $reason): DoNotContact
|
||||
{
|
||||
$dnc = new DoNotContact();
|
||||
$dnc->setChannel($channel);
|
||||
$dnc->setLead($contact);
|
||||
$dnc->setReason($reason);
|
||||
$dnc->setDateAdded(new \DateTime());
|
||||
$this->em->persist($dnc);
|
||||
|
||||
return $dnc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\EventListener;
|
||||
|
||||
use Mautic\AssetBundle\Entity\DownloadRepository;
|
||||
use Mautic\AssetBundle\EventListener\ReportSubscriber;
|
||||
use Mautic\ChannelBundle\Helper\ChannelListHelper;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Model\CompanyReportData;
|
||||
use Mautic\LeadBundle\Report\DncReportService;
|
||||
use Mautic\LeadBundle\Segment\Query\QueryBuilder;
|
||||
use Mautic\ReportBundle\Entity\Report;
|
||||
use Mautic\ReportBundle\Event\ReportBuilderEvent;
|
||||
use Mautic\ReportBundle\Event\ReportGeneratorEvent;
|
||||
use Mautic\ReportBundle\Helper\ReportHelper;
|
||||
use PHPUnit\Framework\Assert;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Contracts\Translation\TranslatorInterface;
|
||||
|
||||
class ReportSubscriberTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private ChannelListHelper $channelListHelper;
|
||||
|
||||
/**
|
||||
* @var CompanyReportData|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $companyReportData;
|
||||
|
||||
/**
|
||||
* @var DownloadRepository|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $downloadRepository;
|
||||
|
||||
/**
|
||||
* @var QueryBuilder|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $queryBuilder;
|
||||
|
||||
/**
|
||||
* @var DncReportService|\PHPUnit\Framework\MockObject\MockObject
|
||||
*/
|
||||
private \PHPUnit\Framework\MockObject\MockObject $dncReportService;
|
||||
|
||||
private ReportHelper $reportHelper;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->queryBuilder = $this->createMock(QueryBuilder::class);
|
||||
$this->channelListHelper = new ChannelListHelper($this->createMock(EventDispatcherInterface::class), $this->createMock(Translator::class));
|
||||
$this->reportHelper = new ReportHelper($this->createMock(EventDispatcherInterface::class));
|
||||
$this->companyReportData = $this->createMock(CompanyReportData::class);
|
||||
$this->downloadRepository = $this->createMock(DownloadRepository::class);
|
||||
$this->dncReportService = $this->createMock(DncReportService::class);
|
||||
}
|
||||
|
||||
public function testOnReportBuilderWithUnknownContext(): void
|
||||
{
|
||||
$companyReportData = new class extends CompanyReportData {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$downloadRepository = new class extends DownloadRepository {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$event = new class extends ReportBuilderEvent {
|
||||
public function __construct()
|
||||
{
|
||||
$this->context = 'unicorn';
|
||||
}
|
||||
};
|
||||
|
||||
$reportSubscriber = new ReportSubscriber($companyReportData, $downloadRepository, $this->dncReportService);
|
||||
|
||||
$reportSubscriber->onReportBuilder($event);
|
||||
|
||||
Assert::assertSame([], $event->getTables());
|
||||
}
|
||||
|
||||
public function testOnReportBuilderWithAssetDownloadContext(): void
|
||||
{
|
||||
$companyReportData = new class extends CompanyReportData {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<mixed>
|
||||
*/
|
||||
public function getCompanyData(): array
|
||||
{
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
$downloadRepository = new class extends DownloadRepository {
|
||||
public function __construct()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
$event = new ReportBuilderEvent($this->createTranslatorMock(), $this->channelListHelper, ReportSubscriber::CONTEXT_ASSET_DOWNLOAD, [], $this->reportHelper);
|
||||
|
||||
$reportSubscriber = new ReportSubscriber($companyReportData, $downloadRepository, $this->dncReportService);
|
||||
|
||||
$reportSubscriber->onReportBuilder($event);
|
||||
|
||||
Assert::assertSame(
|
||||
[
|
||||
'alias' => 'download_count',
|
||||
'label' => '[trans]mautic.asset.report.download_count[/trans]',
|
||||
'type' => 'int',
|
||||
],
|
||||
$event->getTables()['assets']['columns']['a.download_count']
|
||||
);
|
||||
|
||||
Assert::assertSame(
|
||||
[
|
||||
'alias' => 'unique_download_count',
|
||||
'label' => '[trans]mautic.asset.report.unique_download_count[/trans]',
|
||||
'type' => 'int',
|
||||
],
|
||||
$event->getTables()['assets']['columns']['a.unique_download_count']
|
||||
);
|
||||
|
||||
Assert::assertSame(
|
||||
[
|
||||
'alias' => 'download_count',
|
||||
'label' => '[trans]mautic.asset.report.download_count[/trans]',
|
||||
'type' => 'int',
|
||||
'formula' => 'COUNT(ad.id)',
|
||||
],
|
||||
$event->getTables()['asset.downloads']['columns']['a.download_count']
|
||||
);
|
||||
|
||||
Assert::assertSame(
|
||||
[
|
||||
'alias' => 'unique_download_count',
|
||||
'label' => '[trans]mautic.asset.report.unique_download_count[/trans]',
|
||||
'type' => 'int',
|
||||
'formula' => 'COUNT(DISTINCT ad.lead_id)',
|
||||
],
|
||||
$event->getTables()['asset.downloads']['columns']['a.unique_download_count']
|
||||
);
|
||||
}
|
||||
|
||||
private function createTranslatorMock(): TranslatorInterface
|
||||
{
|
||||
return new class implements TranslatorInterface {
|
||||
/**
|
||||
* @param array<int|string> $parameters
|
||||
*/
|
||||
public function trans(string $id, array $parameters = [], ?string $domain = null, ?string $locale = null): string
|
||||
{
|
||||
return '[trans]'.$id.'[/trans]';
|
||||
}
|
||||
|
||||
public function getLocale(): string
|
||||
{
|
||||
return 'en';
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public function testGroupByDefaultConfigured(): void
|
||||
{
|
||||
$report = new Report();
|
||||
$report->setSource(ReportSubscriber::CONTEXT_ASSET_DOWNLOAD);
|
||||
$event = new ReportGeneratorEvent($report, [], $this->queryBuilder, $this->channelListHelper);
|
||||
$subscriber = new ReportSubscriber($this->companyReportData, $this->downloadRepository, $this->dncReportService);
|
||||
$this->queryBuilder->method('from')->willReturn($this->queryBuilder);
|
||||
|
||||
$this->queryBuilder->expects($this->once())
|
||||
->method('groupBy')
|
||||
->with('ad.id');
|
||||
|
||||
$this->assertFalse($event->hasGroupBy());
|
||||
|
||||
$subscriber->onReportGenerate($event);
|
||||
}
|
||||
|
||||
public function testGroupByNotDefaultConfigured(): void
|
||||
{
|
||||
$report = new Report();
|
||||
$report->setSource(ReportSubscriber::CONTEXT_ASSET_DOWNLOAD);
|
||||
$this->queryBuilder->method('from')->willReturn($this->queryBuilder);
|
||||
$report->setGroupBy(['a.id' => 'desc']);
|
||||
$event = new ReportGeneratorEvent($report, [], $this->queryBuilder, $this->channelListHelper);
|
||||
$subscriber = new ReportSubscriber($this->companyReportData, $this->downloadRepository, $this->dncReportService);
|
||||
$subscriber->onReportGenerate($event);
|
||||
$this->assertTrue($event->hasGroupBy());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,287 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Mautic\AssetBundle\Tests\Model;
|
||||
|
||||
use Doctrine\ORM\EntityManager;
|
||||
use Mautic\AssetBundle\AssetEvents;
|
||||
use Mautic\AssetBundle\Entity\Asset;
|
||||
use Mautic\AssetBundle\Entity\AssetRepository;
|
||||
use Mautic\AssetBundle\Entity\Download;
|
||||
use Mautic\AssetBundle\Model\AssetModel;
|
||||
use Mautic\CacheBundle\Cache\CacheProvider;
|
||||
use Mautic\CategoryBundle\Model\CategoryModel;
|
||||
use Mautic\CoreBundle\Entity\IpAddress;
|
||||
use Mautic\CoreBundle\Helper\CoreParametersHelper;
|
||||
use Mautic\CoreBundle\Helper\IpLookupHelper;
|
||||
use Mautic\CoreBundle\Helper\UserHelper;
|
||||
use Mautic\CoreBundle\Security\Permissions\CorePermissions;
|
||||
use Mautic\CoreBundle\Translation\Translator;
|
||||
use Mautic\LeadBundle\Entity\Lead;
|
||||
use Mautic\LeadBundle\Model\LeadModel;
|
||||
use Mautic\LeadBundle\Tracker\ContactTracker;
|
||||
use Mautic\LeadBundle\Tracker\Factory\DeviceDetectorFactory\DeviceDetectorFactory;
|
||||
use Mautic\LeadBundle\Tracker\Service\DeviceCreatorService\DeviceCreatorService;
|
||||
use Mautic\LeadBundle\Tracker\Service\DeviceTrackingService\DeviceTrackingServiceInterface;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
use Symfony\Component\HttpFoundation\ServerBag;
|
||||
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
|
||||
|
||||
class AssetModelTest extends \PHPUnit\Framework\TestCase
|
||||
{
|
||||
private AssetModel $assetModel;
|
||||
|
||||
private CoreParametersHelper&MockObject $coreParametersHelper;
|
||||
|
||||
private ContainerInterface&MockObject $container;
|
||||
|
||||
private CacheProvider $cacheProvider;
|
||||
|
||||
private LeadModel&MockObject $leadModel;
|
||||
|
||||
private CategoryModel&MockObject $categoryModel;
|
||||
|
||||
private RequestStack&MockObject $requestStack;
|
||||
|
||||
private IpLookupHelper&MockObject $ipLookupHelper;
|
||||
|
||||
private DeviceDetectorFactory $deviceDetectorFactory;
|
||||
|
||||
private DeviceCreatorService $deviceCreatorService;
|
||||
|
||||
private DeviceTrackingServiceInterface&MockObject $deviceTrackingService;
|
||||
|
||||
private ContactTracker&MockObject $contactTracker;
|
||||
|
||||
private EntityManager&MockObject $entityManager;
|
||||
|
||||
private CorePermissions&MockObject $corePermissions;
|
||||
|
||||
private EventDispatcherInterface&MockObject $eventDispatcher;
|
||||
|
||||
private MockObject&UrlGeneratorInterface $urlGenerator;
|
||||
|
||||
private Translator&MockObject $translator;
|
||||
|
||||
private UserHelper&MockObject $userHelper;
|
||||
|
||||
private LoggerInterface&MockObject $logger;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->coreParametersHelper = $this->createMock(CoreParametersHelper::class);
|
||||
|
||||
$this->coreParametersHelper->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->equalTo('max_size'))
|
||||
->willReturn('2MB');
|
||||
|
||||
$this->container = $this->createMock(ContainerInterface::class);
|
||||
$this->cacheProvider = new CacheProvider($this->coreParametersHelper, $this->container);
|
||||
$this->leadModel = $this->createMock(LeadModel::class);
|
||||
$this->categoryModel = $this->createMock(CategoryModel::class);
|
||||
$this->requestStack = $this->createMock(RequestStack::class);
|
||||
$this->ipLookupHelper = $this->createMock(IpLookupHelper::class);
|
||||
$this->deviceDetectorFactory = new DeviceDetectorFactory($this->cacheProvider);
|
||||
$this->deviceCreatorService = new DeviceCreatorService();
|
||||
$this->deviceTrackingService = $this->createMock(DeviceTrackingServiceInterface::class);
|
||||
$this->contactTracker = $this->createMock(ContactTracker::class);
|
||||
$this->entityManager = $this->createMock(EntityManager::class);
|
||||
$this->corePermissions = $this->createMock(CorePermissions::class);
|
||||
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
|
||||
$this->urlGenerator = $this->createMock(UrlGeneratorInterface::class);
|
||||
$this->translator = $this->createMock(Translator::class);
|
||||
$this->userHelper = $this->createMock(UserHelper::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->assetModel = new AssetModel(
|
||||
$this->leadModel,
|
||||
$this->categoryModel,
|
||||
$this->requestStack,
|
||||
$this->ipLookupHelper,
|
||||
$this->deviceCreatorService,
|
||||
$this->deviceDetectorFactory,
|
||||
$this->deviceTrackingService,
|
||||
$this->contactTracker,
|
||||
$this->entityManager,
|
||||
$this->corePermissions,
|
||||
$this->eventDispatcher,
|
||||
$this->urlGenerator,
|
||||
$this->translator,
|
||||
$this->userHelper,
|
||||
$this->logger,
|
||||
$this->coreParametersHelper,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that TrackDownload works only with a request.
|
||||
*/
|
||||
public function testTrackDownloadRequest(): void
|
||||
{
|
||||
$asset = new Asset();
|
||||
|
||||
$this->corePermissions->expects($this->once())
|
||||
->method('isAnonymous')
|
||||
->willReturn(true);
|
||||
|
||||
$this->requestStack->expects($this->once())
|
||||
->method('getCurrentRequest')
|
||||
->willReturn(null);
|
||||
|
||||
$this->entityManager->expects($this->never())
|
||||
->method('persist');
|
||||
|
||||
$this->entityManager->expects($this->never())
|
||||
->method('flush');
|
||||
|
||||
$this->entityManager->expects($this->never())
|
||||
->method('detach');
|
||||
|
||||
$this->assetModel->trackDownload($asset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that TrackDownload works successfully.
|
||||
*/
|
||||
public function testTrackDownload(): void
|
||||
{
|
||||
$asset = new Asset();
|
||||
$lead = new Lead();
|
||||
|
||||
$this->corePermissions->expects($this->once())
|
||||
->method('isAnonymous')
|
||||
->willReturn(true);
|
||||
|
||||
$request = $this->createMock(Request::class);
|
||||
|
||||
$serverBag = $this->createMock(ServerBag::class);
|
||||
|
||||
$serverBag->expects($this->once())
|
||||
->method('get')
|
||||
->with($this->equalTo('HTTP_REFERER'))
|
||||
->willReturn('http://localhost');
|
||||
|
||||
$request->server = $serverBag;
|
||||
$matcher = $this->exactly(6);
|
||||
|
||||
$request->expects($matcher)
|
||||
->method('get')->willReturnCallback(function (...$parameters) use ($matcher) {
|
||||
if (1 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals('utm_campaign', $parameters[0]);
|
||||
|
||||
return 'test_utm_campaign';
|
||||
}
|
||||
if (2 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals('utm_content', $parameters[0]);
|
||||
|
||||
return 'test_utm_content';
|
||||
}
|
||||
if (3 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals('utm_medium', $parameters[0]);
|
||||
|
||||
return 'test_utm_medium';
|
||||
}
|
||||
if (4 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals('utm_source', $parameters[0]);
|
||||
|
||||
return 'test_utm_source';
|
||||
}
|
||||
if (5 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals('utm_term', $parameters[0]);
|
||||
|
||||
return 'test_utm_term';
|
||||
}
|
||||
if (6 === $matcher->numberOfInvocations()) {
|
||||
$this->assertEquals('ct', $parameters[0]);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
$this->requestStack->expects($this->once())
|
||||
->method('getCurrentRequest')
|
||||
->willReturn($request);
|
||||
|
||||
$this->deviceTrackingService->expects($this->once())
|
||||
->method('isTracked')
|
||||
->willReturn(false);
|
||||
|
||||
$this->contactTracker->expects($this->once())
|
||||
->method('getContact')
|
||||
->willReturn($lead);
|
||||
|
||||
$this->deviceTrackingService->expects($this->once())
|
||||
->method('getTrackedDevice')
|
||||
->willReturn(null);
|
||||
|
||||
$assetRepository = $this->createMock(AssetRepository::class);
|
||||
|
||||
$this->entityManager->expects($this->once())
|
||||
->method('getRepository')
|
||||
->with($this->equalTo(Asset::class))
|
||||
->willReturn($assetRepository);
|
||||
|
||||
$assetRepository->expects($this->once())
|
||||
->method('upDownloadCount')
|
||||
->with(
|
||||
$this->equalTo($asset->getId()),
|
||||
$this->equalTo(1),
|
||||
$this->equalTo(true),
|
||||
);
|
||||
|
||||
$ipAddress = new IpAddress('127.0.0.1');
|
||||
|
||||
$this->ipLookupHelper->expects($this->once())
|
||||
->method('getIpAddress')
|
||||
->willReturn($ipAddress);
|
||||
|
||||
$this->eventDispatcher->expects($this->once())
|
||||
->method('hasListeners')
|
||||
->with($this->equalTo(AssetEvents::ASSET_ON_LOAD))
|
||||
->willReturn(false);
|
||||
|
||||
/** @var ?Download $download */
|
||||
$download = null;
|
||||
|
||||
$this->entityManager->expects($this->once())
|
||||
->method('persist')
|
||||
->with($this->callback(function ($downloadPersist) use (&$download) {
|
||||
$download = $downloadPersist;
|
||||
|
||||
return $download instanceof Download;
|
||||
}));
|
||||
|
||||
$this->entityManager->expects($this->once())
|
||||
->method('flush');
|
||||
|
||||
$this->entityManager->expects($this->once())
|
||||
->method('detach')
|
||||
->with($this->callback(function ($downloadDetach) use (&$download) {
|
||||
$this->assertSame($downloadDetach, $download);
|
||||
|
||||
return true;
|
||||
}));
|
||||
|
||||
$this->assetModel->trackDownload($asset);
|
||||
|
||||
$this->assertEquals('test_utm_campaign', $download->getUtmCampaign());
|
||||
$this->assertEquals('test_utm_content', $download->getUtmContent());
|
||||
$this->assertEquals('test_utm_medium', $download->getUtmMedium());
|
||||
$this->assertEquals('test_utm_source', $download->getUtmSource());
|
||||
$this->assertEquals('test_utm_term', $download->getUtmTerm());
|
||||
$this->assertEquals('200', $download->getCode());
|
||||
$this->assertEquals($ipAddress, $download->getIpAddress());
|
||||
$this->assertEquals($lead, $download->getLead());
|
||||
$this->assertEquals($asset, $download->getAsset());
|
||||
$this->assertEquals('http://localhost', $download->getReferer());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user