media_acquiadam-8.x-1.46/tests/src/Unit/AssetRefreshManagerTest.php
tests/src/Unit/AssetRefreshManagerTest.php
<?php
namespace Drupal\Tests\media_acquiadam\Unit;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Entity\Query\Null\Query;
use Drupal\Core\Language\Language;
use Drupal\Core\Language\LanguageManagerInterface;
use Drupal\Core\Queue\DatabaseQueue;
use Drupal\Core\Queue\QueueFactory;
use Drupal\Core\State\State;
use Drupal\media_acquiadam\Acquiadam;
use Drupal\media_acquiadam\Exception\InvalidCredentialsException;
use Drupal\media_acquiadam\Service\AssetRefreshManager;
use Drupal\Tests\media_acquiadam\Traits\AcquiadamConfigTrait;
use Drupal\Tests\media_acquiadam\Traits\AcquiadamLoggerFactoryTrait;
use Drupal\Tests\UnitTestCase;
use GuzzleHttp\Exception\GuzzleException;
/**
* AssetRefreshManager Service test.
*
* @coversDefaultClass \Drupal\media_acquiadam\Service\AssetRefreshManager
*
* @group media_acquiadam
*/
class AssetRefreshManagerTest extends UnitTestCase {
use AcquiadamLoggerFactoryTrait, AcquiadamConfigTrait;
protected const LAST_EDITED_DATE_QUERY = '(lastEditDate:[after 2022-05-24T08:14:42Z]) AND (lastEditDate:[before 2022-05-24T14:17:00Z])';
protected const REQUEST_TIME = 1560000000;
/**
* DI container.
*
* @var \Drupal\Core\DependencyInjection\ContainerBuilder
*/
protected $container;
/**
* AssetRefreshManager service.
*
* @var \Drupal\media_acquiadam\Service\AssetRefreshManagerInterface
*/
protected $assetRefreshManager;
/**
* The Queue Worker.
*
* @var \Drupal\Core\Queue\QueueInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $queue;
/**
* The media entity query.
*
* @var \Drupal\Core\Entity\Query\QueryInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $entityQuery;
/**
* The Acquiadam Service.
*
* @var \Drupal\media_acquiadam\AcquiadamInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $acquiadamClient;
/**
* The Drupal State Service.
*
* @var \Drupal\Core\State\StateInterface|\PHPUnit\Framework\MockObject\MockObject
*/
protected $state;
/**
* Validate that we can modify the request limit property correctly.
*/
public function testRequestLimitGetterSetter() {
$expected = 3;
$this->assertEquals($expected, $this->assetRefreshManager->getRequestLimit());
$original = $this->assetRefreshManager->setRequestLimit($expected * 2);
$this->assertEquals($expected, $original);
$original = $this->assetRefreshManager->setRequestLimit($expected);
$this->assertEquals($expected * 2, $original);
}
/**
* Tests a "no asset id fields provided" scenario.
*/
public function testEmptyAssetIdFields() {
$actual = $this->assetRefreshManager->updateQueue([]);
$this->assertEquals(0, $actual);
}
/**
* Tests a "no matching media entity ids are found" scenario.
*
* @param array $request_query_options
* The list of request query options.
* @param array $response
* The stub of Search API response.
* @param array $expected_asset_ids
* The list of expected asset ids.
*
* @dataProvider providerTestInterruptedFetch
*/
public function testNoMatchingMediaEntityIds(array $request_query_options, array $response, array $expected_asset_ids) {
$this->setupApiResponseStub($request_query_options, $response);
$this->entityQuery->expects($this->any())
->method('orConditionGroup')
->will($this->returnSelf());
$this->entityQuery->expects($this->any())
->method('condition')
->willReturnOnConsecutiveCalls(
[$this->equalTo('bundle'), $this->equalTo('test_bundle')],
[$this->equalTo('field_1'), $this->equalTo($expected_asset_ids)])
->will($this->returnSelf());
$this->entityQuery->expects($this->any())
->method('execute')
->willReturn([]);
$this->queue->expects($this->never())
->method($this->anything());
$actual = $this->assetRefreshManager->updateQueue($this->getAssetIdFieldsStub());
$this->assertEquals(0, $actual);
}
/**
* Tests a "non-interrupted API fetch" scenario.
*
* @param array $request_query_options
* The list of request query options.
* @param array $response
* The stub of Search API response.
* @param array $expected_asset_ids
* The list of expected asset ids.
* @param int $expected_total
* The expected number of media entities to add to the queue.
*
* @dataProvider providerTestNonInterruptedFetch
*/
public function testNonInterruptedFetch(array $request_query_options, array $response, array $expected_asset_ids, int $expected_total) {
$this->setupApiResponseStub($request_query_options, $response);
$this->setupMediaEntityExpectations($expected_asset_ids, $expected_total);
$actual = $this->assetRefreshManager->updateQueue($this->getAssetIdFieldsStub());
$this->assertEquals($expected_total, $actual);
}
/**
* Tests when the cut-off is before the last sync time.
*
* @param string $last_sync_offset
* The offset for \DateTime::modify().
* @param bool $expect_results
* TRUE if results expected, FALSE if not.
*
* @dataProvider provideTimeOffsets
*/
public function testCutoffBeforeLastSync(string $last_sync_offset, bool $expect_results) {
$real_time = new \DateTime('now', new \DateTimeZone('UTC'));
$sync_time = new \DateTime('now', new \DateTimeZone('UTC'));
$sync_time->modify($last_sync_offset);
$state = $this->createMock(State::class);
$state->method('get')
->with('media_acquiadam.last_sync')
->willReturn($sync_time->getTimestamp());
$time = $this->createMock(TimeInterface::class);
$time->method('getCurrentTime')->willReturn($real_time->getTimestamp());
$asset_before_time = (clone $real_time)->modify('-1 hour');
$this->setupApiResponseStub([
'limit' => 3,
'offset' => 0,
'query' => "(lastEditDate:[after {$sync_time->format('Y-m-d\TH:i:s\Z')}]) AND (lastEditDate:[before {$asset_before_time->format('Y-m-d\TH:i:s\Z')}])",
'include_deleted' => 'true',
'include_archived' => 'true',
], [
'total_count' => 3,
'assets' => [
(object) ['id' => '3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
(object) ['id' => '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
(object) ['id' => '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
],
]);
$this->setupMediaEntityExpectations(
['3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
3
);
$asset_refresh_manager = new AssetRefreshManager(
$this->container->get('media_acquiadam.acquiadam'),
$state,
$this->container->get('logger.factory'),
$this->container->get('queue'),
$this->container->get('entity_type.manager'),
$this->container->get('config.factory'),
$time
);
$asset_refresh_manager->setRequestLimit(3);
$actual = $asset_refresh_manager->updateQueue($this->getAssetIdFieldsStub());
$this->assertEquals($expect_results ? 3 : 0, $actual);
}
/**
* Provides time offsets for verifying cut-off preflight check.
*
* The offset is for the last sync time from the current test time.
*
* @return \Generator
* The test data sets.
*/
public function provideTimeOffsets() {
yield '+2 minutes' => ['+2 minutes', FALSE];
yield '-1 hour' => ['-1 hour', TRUE];
yield '-90 minutes' => ['-90 minutes', TRUE];
}
/**
* Tests that transcoding at 'original' does not delay sync.
*/
public function testNoDelayOnOriginal() {
$this->setupApiResponseStub([
'limit' => 3,
'offset' => 0,
'query' => 'lastEditDate:[after 2022-05-24T08:14:42Z]',
'include_deleted' => 'true',
'include_archived' => 'true',
], [
'total_count' => 3,
'assets' => [
(object) ['id' => '3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
(object) ['id' => '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
(object) ['id' => '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
],
]);
$this->setupMediaEntityExpectations(
['3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
3
);
$config_factory = $this->getConfigFactoryStub([
'media_acquiadam.settings' => [
'token' => 'demo/121someRandom1342test32st',
'sync_interval' => 3600,
'sync_method' => "updated_date",
'transcode' => 'original',
'sync_perform_delete' => 1,
'size_limit' => 1280,
'report_asset_usage' => 1,
'domain' => 'subdomain.widencollective.com',
'client_id' => 'a3mf039fd77dw67886459q90098z0980.app.widen.com',
],
'system.file' => ['default_scheme' => 'public'],
'media.settings' => ['icon_base_uri' => 'public://media-icons'],
]);
$asset_refresh_manager = new AssetRefreshManager(
$this->container->get('media_acquiadam.acquiadam'),
$this->container->get('state'),
$this->container->get('logger.factory'),
$this->container->get('queue'),
$this->container->get('entity_type.manager'),
$config_factory,
$this->container->get('datetime.time')
);
$asset_refresh_manager->setRequestLimit(3);
$actual = $asset_refresh_manager->updateQueue($this->getAssetIdFieldsStub());
$this->assertEquals(3, $actual);
}
/**
* Tests an "interrupted API fetch (a result set exceeds the limit)" scenario.
*
* @param array $request_query_options
* The list of request query options.
* @param array $response
* The stub of Search API response.
* @param array $expected_asset_ids
* The list of expected asset ids.
* @param int $expected_total
* The expected number of media entities to add to the queue.
*
* @dataProvider providerTestInterruptedFetch
*/
public function testInterruptedFetch(array $request_query_options, array $response, array $expected_asset_ids, int $expected_total) {
$this->setupApiResponseStub($request_query_options, $response);
$this->setupMediaEntityExpectations($expected_asset_ids, $expected_total);
$actual = $this->assetRefreshManager->updateQueue($this->getAssetIdFieldsStub());
$this->assertEquals(3, $actual);
}
/**
* Tests a "failed API request" scenario.
*
* @param \Throwable $exception_stub
* Exception object stub.
*
* @dataProvider providerTestFailedApiRequest
*/
public function testFailedApiRequest(\Throwable $exception_stub) {
$this->acquiadamClient
->method('searchAssets')
->will($this->throwException($exception_stub));
$actual = $this->assetRefreshManager->updateQueue($this->getAssetIdFieldsStub());
$this->assertEquals(0, $actual);
}
/**
* Provides test data for not-interrupted fetches (normal) related tests.
*
* @return array
* Test data (request query options, response, expected asset ids).
*/
public function providerTestNonInterruptedFetch() {
// Asset Objects.
$asset_obj1 = new \StdClass();
$asset_obj1->id = '3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a';
$asset_obj2 = new \StdClass();
$asset_obj2->id = '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a';
$asset_obj3 = new \StdClass();
$asset_obj3->id = '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a';
return [
[
[
'limit' => 3,
'offset' => 0,
'query' => self::LAST_EDITED_DATE_QUERY,
'include_deleted' => 'true',
'include_archived' => 'true',
],
[
'total_count' => 3,
'assets' => [
$asset_obj1,
$asset_obj2,
$asset_obj3,
],
],
['3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
3,
],
// No results.
[
[
'limit' => 3,
'offset' => 0,
'query' => self::LAST_EDITED_DATE_QUERY,
'include_deleted' => 'true',
'include_archived' => 'true',
],
[
'total_count' => 0,
'assets' => [],
],
[],
0,
],
];
}
/**
* Provides test data for interrupted fetches related tests.
*
* @return array
* Test data (request query options, response, expected asset ids).
*/
public function providerTestInterruptedFetch() {
// Asset Objects.
$asset_obj1 = new \StdClass();
$asset_obj1->id = '3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a';
$asset_obj2 = new \StdClass();
$asset_obj2->id = '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a';
$asset_obj3 = new \StdClass();
$asset_obj3->id = '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a';
return [
[
[
'limit' => 3,
'offset' => 0,
'query' => self::LAST_EDITED_DATE_QUERY,
'include_deleted' => 'true',
'include_archived' => 'true',
],
[
'total_count' => 3,
'assets' => [
$asset_obj1,
$asset_obj2,
$asset_obj3,
],
],
['3f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '4f9bf79b-4fee-49f1-a852-3fdb7ca60f2a', '5f9bf79b-4fee-49f1-a852-3fdb7ca60f2a'],
3,
],
];
}
/**
* Provides test data for testing failed API requests.
*
* @return \Throwable[]
* Test data (GuzzleException and InvalidCredentialsException stubs).
*/
public function providerTestFailedApiRequest(): array {
return [
[
new /* @noinspection PhpSuperClassIncompatibleWithInterfaceInspection */
class() extends \Exception implements GuzzleException {},
],
[new InvalidCredentialsException()],
];
}
/**
* Returns asset id fields stub.
*
* @return array
* Asset id fields stub where key is the bundle and value is the field name.
*/
protected function getAssetIdFieldsStub() {
return ['test_bundle' => 'field_1'];
}
/**
* Setups the API response stub.
*
* @param array $request_query_options
* The list of request query options.
* @param array $response
* The stub of Search API response.
*/
protected function setupApiResponseStub(array $request_query_options, array $response) {
$this->acquiadamClient
->method('searchAssets')
->willReturnCallback(function () use ($response, $request_query_options) {
return $response;
} );
}
/**
* Setups the media entity query execution expectations.
*
* @param array $expected_asset_ids
* The list of expected asset ids.
* @param int $expected_total
* The expected number of media entities to add to the queue.
*/
protected function setupMediaEntityExpectations(array $expected_asset_ids, int $expected_total) {
$this->entityQuery->expects($this->any())
->method('orConditionGroup')
->will($this->returnSelf());
$this->entityQuery->expects($this->any())
->method('condition')
->willReturnOnConsecutiveCalls(
[$this->equalTo('bundle'), $this->equalTo('test_bundle')],
[$this->equalTo('field_1'), $this->equalTo($expected_asset_ids)]
)
->will($this->returnSelf());
if (!$expected_asset_ids) {
return;
}
$this->entityQuery->expects($this->any())
->method('execute')
->willReturn(range(1, $expected_total));
$unit_test = $this;
$expected_queue_items = array_map(
function ($value) use ($unit_test) {
return [$unit_test->equalTo(['media_id' => $value])];
},
range(1, $expected_total)
);
$this->queue->expects($this->any())
->method('createItem')
->willReturnOnConsecutiveCalls(
...$expected_queue_items
);
}
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->acquiadamClient = $this->getMockBuilder(Acquiadam::class)
->addMethods(['searchAssets'])
->disableOriginalConstructor()
->getMock();
$this->state = $this->createMock(State::class);
$this->state->method('get')
->with('media_acquiadam.last_sync')
// 2022-05-24T09:14:42Z UTC.
->willReturn('1653383682');
$this->queue = $this->createMock(DatabaseQueue::class);
/** @var \Drupal\Core\Queue\QueueFactory|\PHPUnit\Framework\MockObject\MockObject $queue_factory */
$queue_factory = $this->createMock(QueueFactory::class);
$queue_factory->method('get')
->willReturn($this->queue);
$this->entityQuery = $this->getMockBuilder(Query::class)
->onlyMethods(['orConditionGroup', 'condition', 'execute'])
->disableOriginalConstructor()
->getMock();
$entity_storage = $this->createMock(EntityStorageInterface::class);
$entity_storage->method('getQuery')->willReturn($this->entityQuery);
$entity_type_manager = $this->createMock(EntityTypeManagerInterface::class);
$entity_type_manager->method('getStorage')->willReturnMap([
['media', $entity_storage],
]);
$language_manager = $this->createMock(LanguageManagerInterface::class);
$language_manager->method('getCurrentLanguage')
->willReturn(new Language(Language::$defaultValues));
$time = $this->createMock(TimeInterface::class);
$time->method('getCurrentTime')
// 2022-05-24T15:17:00Z UTC.
->willReturn('1653405420');
$this->container = new ContainerBuilder();
$this->container->set('config.factory', $this->getDefaultConfigFactoryStub());
$this->container->set('state', $this->state);
$this->container->set('logger.factory', $this->getLoggerFactoryStub());
$this->container->set('queue', $queue_factory);
$this->container->set('entity_type.manager', $entity_type_manager);
$this->container->set('media_acquiadam.acquiadam', $this->acquiadamClient);
$this->container->set('language_manager', $language_manager);
$this->container->set('datetime.time', $time);
\Drupal::setContainer($this->container);
$this->assetRefreshManager = AssetRefreshManager::create($this->container);
$this->assetRefreshManager->setRequestLimit(3);
}
}
