xero_sync-8.x-1.x-dev/tests/src/Unit/EntityHandlerTest.php
tests/src/Unit/EntityHandlerTest.php
<?php
namespace Drupal\Tests\xero_sync\Unit;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\DataDefinitionInterface;
use Drupal\Core\TypedData\Plugin\DataType\StringData;
use Drupal\user\UserInterface;
use Drupal\Tests\xero\Unit\Plugin\DataType\TestBase;
use Drupal\xero_sync\Event\XeroSyncAppropriateEvent;
use Drupal\xero_sync\XeroSyncEntityHandler;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\xero_sync\XeroSyncJobManagerInterface;
use Drupal\xero_sync\XeroSyncSynchronizerInterface;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
/**
* @coversDefaultClass \Drupal\xero_sync\XeroSyncEntityHandler
* @group xero_sync
*/
class EntityHandlerTest extends TestBase {
const XERO_TYPE = 'xero_contact';
const XERO_TYPE_CLASS = '\Drupal\xero\Plugin\DataType\Contact';
const XERO_DEFINITION_CLASS = '\Drupal\xero\TypedData\Definition\ContactDefinition';
/**
* The job manager.
*
* @var \Drupal\xero_sync\XeroSyncJobManager|\PHPUnit\Framework\MockObject\MockObject
*/
protected $jobManager;
/**
* The Xero Sync entity handler.
*
* @var \Drupal\xero_sync\XeroSyncEntityHandler
*/
protected $entityHandler;
/**
* The Xero Sync synchronizer.
*
* @var \Drupal\xero_sync\XeroSyncSynchronizer
*/
protected $synchronizer;
/**
* The entity type manager.
*
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entityTypeManager;
/**
* The event dispatcher.
*
* @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
*/
protected $eventDispatcher;
/**
* The event dispatcher.
*
* @var \Drupal\xero_sync\Event\XeroSyncPropertiesEvent
*/
protected $event;
/**
* Whether or not sync is appropriate for the entity in contexts.
*
* @var array
*/
protected $appropriatenessFlags = [
'create' => TRUE,
'update' => TRUE,
];
/**
* The entity to sync.
*
* @var \Drupal\Core\Entity\EntityInterface
*/
protected $entity;
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->jobManager = $this->createMock(XeroSyncJobManagerInterface::class);
$this->synchronizer = $this->createMock(XeroSyncSynchronizerInterface::class);
$this->entityTypeManager = $this->createMock(EntityTypeManagerInterface::class);
$this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
$this->entityHandler = new XeroSyncEntityHandler($this->jobManager, $this->synchronizer, $this->typedDataManager, $this->entityTypeManager, $this->eventDispatcher);
$this->entity = $this->createMock(UserInterface::class);
$this->entity->expects($this->any())
->method('getEntityTypeId')
->willReturn('user');
$this->event = $this->createMock(XeroSyncAppropriateEvent::class);
$this->eventDispatcher->expects($this->any())
->method('dispatch')
->with($this->isInstanceOf(XeroSyncAppropriateEvent::class), 'xero_sync.sync_is_appropriate.user')
->willReturn($this->event);
// Because XeroEntityUtilitiesTrait::buildXeroItemWithId calls
// the item's static getXeroProperty method and then sets a property,
// we can't use a mock item and so must elaborately prepare a real item.
$type_class = self::XERO_TYPE_CLASS;
$item = new $type_class($this->dataDefinition, self::XERO_TYPE);
$this->typedDataManager->expects($this->any())
->method('createDataDefinition')
->willReturn($this->createMock(DataDefinitionInterface::class));
$this->typedDataManager->expects($this->any())
->method('create')
->willReturn($item);
$string_def = DataDefinition::create('string');
$defaultProperty = new StringData($string_def);
$this->typedDataManager->expects($this->any())
->method('getPropertyInstance')
->willReturn($defaultProperty);
}
/**
* Test the EntityHandler's getAppropriatenessFlags method.
*
* @param array $appropriatenessFlags
* Whether syncing is appropriate for the entity in various contexts.
*
* @dataProvider appropriatenessProvider
*
* @covers \Drupal\xero_sync\XeroSyncEntityHandler::getAppropriatenessFlags
*/
public function testGetAppropriatenessFlags(array $appropriatenessFlags) {
$this->event->expects($this->any())
->method('getFlags')
->willReturn($appropriatenessFlags);
$actual = $this->entityHandler->getAppropriatenessFlags($this->entity);
$this->assertSame($appropriatenessFlags, $actual);
}
/**
* Data provider for tests that need to test the appropriateness flags.
*/
public function appropriatenessProvider() {
return [
[
[
'create' => TRUE,
'update' => TRUE,
],
],
[
[
'create' => TRUE,
'update' => FALSE,
],
],
[
[
'create' => FALSE,
'update' => TRUE,
],
],
[
[
'create' => FALSE,
'update' => FALSE,
],
],
];
}
/**
* Test the EntityHandler's unsyncIfAppropriate method.
*
* @param bool $isUpdateAppropriate
* Whether or not the entity should be synced.
* @param bool $isAlreadySynced
* Whether or not the entity has already been synced.
* @param bool $expectUnsync
* Whether or not to expect the entity to be unsynced.
*
* @dataProvider unsyncIfAppropriateProvider
*
* @covers \Drupal\xero_sync\XeroSyncEntityHandler::unsyncIfAppropriate
*/
public function testUnsyncIfAppropriate($isUpdateAppropriate, $isAlreadySynced, $expectUnsync) {
$flags = [
'create' => FALSE,
'update' => $isUpdateAppropriate,
];
$this->event->expects($this->any())
->method('getFlags')
->willReturn($flags);
$xeroId = $isAlreadySynced ? 'A' : NULL;
$xeroType = $this->randomMachineName();
$fieldName = $this->entityHandler->getFieldName($this->entity->getEntityTypeId());
$this->entity->expects($this->any())
->method('hasField')
->with($fieldName)
->willReturn(TRUE);
$this->entity->expects($this->any())
->method('get')
->with($fieldName)
->willReturn($this->getMockXeroField($xeroType, $xeroId));
if ($expectUnsync) {
$this->jobManager->expects($this->once())
->method('queueEntityUnsync')
->with($this->entity, NULL, NULL, FALSE);
}
else {
$this->jobManager->expects($this->never())
->method('queueEntityUnsync');
}
$this->entityHandler->unsyncIfAppropriate($this->entity);
}
/**
* Data provider for testUnsyncIfAppropriate.
*/
public function unsyncIfAppropriateProvider() {
return [
[
'isUpdateAppropriate' => TRUE,
'isAlreadySynced' => TRUE,
'expectUnsync' => TRUE,
],
[
'isUpdateAppropriate' => TRUE,
'isAlreadySynced' => FALSE,
'expectUnsync' => TRUE,
],
[
'isUpdateAppropriate' => FALSE,
'isAlreadySynced' => TRUE,
'expectUnsync' => TRUE,
],
[
'isUpdateAppropriate' => FALSE,
'isAlreadySynced' => FALSE,
'expectUnsync' => FALSE,
],
];
}
/**
* Test the EntityHandler's handleInsert method.
*
* @param array $appropriatenessFlags
* Whether syncing is appropriate for the entity.
*
* @dataProvider appropriatenessProvider
*
* @covers \Drupal\xero_sync\XeroSyncEntityHandler::handleInsert
*/
public function testHandleInsert(array $appropriatenessFlags) {
$this->event->expects($this->any())
->method('getFlags')
->willReturn($appropriatenessFlags);
if ($appropriatenessFlags['create'] || $appropriatenessFlags['update']) {
$this->jobManager->expects($this->once())
->method('queueEntitySync')
->with($this->entity);
}
else {
$this->jobManager->expects($this->never())
->method('queueEntitySync');
}
$this->entityHandler->handleInsert($this->entity);
}
/**
* Test the EntityHandler's handleDelete method.
*
* @param bool $isUpdateAppropriate
* Whether or not the entity should be synced.
* @param bool $isAlreadySynced
* Whether or not the entity has already been synced.
* @param bool $expectUnsync
* Whether or not to expect the entity to be unsynced.
*
* @dataProvider handleDeleteProvider
*
* @covers \Drupal\xero_sync\XeroSyncEntityHandler::handleDelete
*/
public function testHandleDelete($isUpdateAppropriate, $isAlreadySynced, $expectUnsync) {
$flags = [
'create' => FALSE,
'update' => $isUpdateAppropriate,
];
$this->event->expects($this->any())
->method('getFlags')
->willReturn($flags);
$xeroId = $isAlreadySynced ? 'A' : NULL;
$xeroType = $this->randomMachineName();
$fieldName = $this->entityHandler->getFieldName($this->entity->getEntityTypeId());
$this->entity->expects($this->any())
->method('hasField')
->with($fieldName)
->willReturn(TRUE);
$this->entity->expects($this->any())
->method('get')
->with($fieldName)
->willReturn($this->getMockXeroField($xeroType, $xeroId));
if ($expectUnsync) {
$this->jobManager->expects($this->once())
->method('queueEntityUnsync')
->with($this->entity, NULL, NULL, TRUE);
}
else {
$this->jobManager->expects($this->never())
->method('queueEntityUnsync');
}
$this->entityHandler->handleDelete($this->entity);
}
/**
* Data provider for testHandleDelete.
*/
public function handleDeleteProvider() {
return [
[
'isUpdateAppropriate' => TRUE,
'isAlreadySynced' => TRUE,
'expectUnsync' => TRUE,
],
[
'isUpdateAppropriate' => TRUE,
'isAlreadySynced' => FALSE,
'expectUnsync' => TRUE,
],
[
'isUpdateAppropriate' => FALSE,
'isAlreadySynced' => TRUE,
'expectUnsync' => TRUE,
],
[
'isUpdateAppropriate' => FALSE,
'isAlreadySynced' => FALSE,
'expectUnsync' => FALSE,
],
];
}
/**
* Test how handleUpdate handles changes in a entity's stored Xero id.
*
* @param bool|null $hasOriginal
* Whether the entity has the 'original' property.
* @param string $previousId
* The Xero id stored on the entity before this update.
* @param string $currentId
* The Xero id stored on the entity which has just been updated.
* @param bool|null $isItemUpdateNeeded
* If bool, then a call to synchronizer::isItemUpdateNeeded should return
* this value, if NULL then there should be no such call.
* @param array $expectedCalls
* The methods on the job manager that should be called.
* @param bool $isUpdateAppropriate
* Whether or not syncing is appropriate for the entity.
*
* @dataProvider handleUpdateProvider
*
* @covers \Drupal\xero_sync\XeroSyncEntityHandler::handleUpdate
*/
public function testHandleUpdate($hasOriginal, $previousId, $currentId, $isItemUpdateNeeded, array $expectedCalls, $isUpdateAppropriate) {
$flags = [
'create' => FALSE,
'update' => $isUpdateAppropriate,
];
$this->event->expects($this->any())
->method('getFlags')
->willReturn($flags);
$xeroType = $this->randomMachineName();
$original = $this->createMock(UserInterface::class);
$fieldName = $this->entityHandler->getFieldName($original->getEntityTypeId());
$original->expects($this->any())
->method('get')
->with($fieldName)
->willReturn($this->getMockXeroField($xeroType, $previousId));
$user = $this->entity;
$user->expects($this->any())
->method('get')
->with($fieldName)
->willReturn($this->getMockXeroField($xeroType, $currentId));
$user->expects($this->any())
->method('hasField')
->with($fieldName)
->willReturn(TRUE);
if ($hasOriginal !== FALSE) {
$user->original = $original;
}
if (is_null($hasOriginal)) {
$user->original = NULL;
}
if (is_null($isItemUpdateNeeded)) {
$this->synchronizer->expects($this->never())
->method('isItemUpdateNeeded');
}
else {
$this->synchronizer->expects($this->once())
->method('isItemUpdateNeeded')
->willReturn($isItemUpdateNeeded);
}
foreach ($expectedCalls as $index => $call) {
if ($call === 'queueEntityUnsync') {
$this->jobManager->expects($this->once())
->method($call)
->with(
$this->isInstanceOf(UserInterface::class),
$this->equalTo($xeroType),
$this->equalTo($previousId)
);
}
elseif ($call === 'queueEntitySync') {
$this->jobManager->expects($this->once())
->method($call)
->with($user);
}
}
$this->entityHandler->handleUpdate($user);
}
/**
* Get a mock Xero reference field object.
*
* @param string $xeroType
* The Xero type stored in the field.
* @param string $xeroId
* The Xero guid stored in the field.
*
* @return \PHPUnit_Framework_MockObject_MockObject
* The mock field obect.
*/
protected function getMockXeroField($xeroType, $xeroId) {
$field = $this->createMock(FieldItemListInterface::class);
if (empty($xeroId)) {
$field->expects($this->any())
->method('isEmpty')
->willReturn(TRUE);
$field->expects($this->never())
->method('first');
}
else {
$field->expects($this->any())
->method('isEmpty')
->willReturn(FALSE);
$field->expects($this->any())
->method('first')
->willReturn((object) [
'type' => $xeroType,
'guid' => $xeroId,
]);
}
return $field;
}
/**
* Data provider for testHandleUpdate.
*/
public function handleUpdateProvider() {
$cases = [
[
'has original' => TRUE,
'previous Xero id' => NULL,
'current Xero id' => NULL,
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntitySync'],
],
[
'has original' => TRUE,
'previous Xero id' => 'A',
'current Xero id' => NULL,
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntityUnsync'],
],
[
'has original' => TRUE,
'previous Xero id' => 'A',
'current Xero id' => 'B',
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => [
'queueEntityUnsync',
'queueEntitySync',
],
],
[
'has original' => TRUE,
'previous Xero id' => NULL,
'current Xero id' => 'B',
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntitySync'],
],
[
'has original' => TRUE,
'previous Xero id' => 'A',
'current Xero id' => 'A',
'isItemUpdateNeeded' => TRUE,
'expected job manager calls' => ['queueEntitySync'],
],
[
'has original' => TRUE,
'previous Xero id' => 'A',
'current Xero id' => 'A',
'isItemUpdateNeeded' => FALSE,
'expected job manager calls' => [],
],
[
'has original' => NULL,
'previous Xero id' => NULL,
'current Xero id' => NULL,
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntitySync'],
],
[
'has original' => NULL,
'previous Xero id' => NULL,
'current Xero id' => 'A',
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntitySync'],
],
[
'has original' => FALSE,
'previous Xero id' => NULL,
'current Xero id' => NULL,
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntitySync'],
],
[
'has original' => FALSE,
'previous Xero id' => NULL,
'current Xero id' => 'A',
'isItemUpdateNeeded' => NULL,
'expected job manager calls' => ['queueEntitySync'],
],
];
$syncAppropriateCases = [];
$syncInappropriateCases = [];
foreach ($cases as $case) {
$case['isUpdateAppropriate'] = TRUE;
$syncAppropriateCases[] = $case;
$case['isUpdateAppropriate'] = FALSE;
$case['expected job manager calls'] = [];
$syncInappropriateCases[] = $case;
}
return array_merge($syncAppropriateCases, $syncInappropriateCases);
}
}
