xero_sync-8.x-1.x-dev/tests/src/Unit/SynchronizerTest.php

tests/src/Unit/SynchronizerTest.php
<?php

namespace Drupal\Tests\xero_sync\Unit;

use Drupal\Core\TypedData\TypedDataManagerInterface;
use Drupal\Tests\UnitTestCase;
use Drupal\xero_sync\Exception\SyncFailureException;
use Drupal\xero_sync\XeroSyncItemFinder;
use Drupal\xero\XeroItemManager;
use Drupal\xero_sync\XeroSyncSynchronizer;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\xero\TypedData\XeroComplexItemInterface;
use Drupal\xero_sync\Event\XeroSyncPropertiesEvent;
use Drupal\xero_sync\XeroSyncInvokedAtIndex as CustomInvokedAtIndexMatcher;

/**
 * @coversDefaultClass \Drupal\xero_sync\XeroSyncSynchronizer
 * @group xero_sync
 */
class SynchronizerTest extends UnitTestCase {

  /**
   * The item finder.
   *
   * @var \Drupal\xero_sync\XeroSyncItemFinder
   */
  protected $itemFinder;

  /**
   * The item manager.
   *
   * @var \Drupal\xero\XeroItemManager
   */
  protected $itemManager;

  /**
   * The event dispatcher.
   *
   * @var \Symfony\Component\EventDispatcher\EventDispatcherInterface
   */
  protected $eventDispatcher;

  /**
   * The typed data manager.
   *
   * @var \Drupal\Core\TypedData\TypedDataManagerInterface
   */
  protected $typedDataManager;

  /**
   * An entity.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  protected $entityA;

  /**
   * An entity.
   *
   * @var \Drupal\Core\Entity\EntityInterface
   */
  protected $entityB;

  /**
   * A Xero item.
   *
   * @var \Drupal\xero\TypedData\XeroComplexItemInterface
   */
  protected $itemA;

  /**
   * A Xero item.
   *
   * @var \Drupal\xero\TypedData\XeroComplexItemInterface
   */
  protected $itemB;

  /**
   * The update item event.
   *
   * @var \Drupal\xero_sync\Event\XeroSyncPropertiesEvent
   */
  protected $updateItemEvent;

  /**
   * The update entity event.
   *
   * @var \Drupal\xero_sync\Event\XeroSyncPropertiesEvent
   */
  protected $updateEntityEvent;

  /**
   * The synchronizer.
   *
   * @var \Drupal\xero_sync\XeroSyncSynchronizer
   */
  protected $synchronizer;

  /**
   * The entity type.
   *
   * @var string
   */
  protected $testEntityType;

  /**
   * The entity id.
   *
   * @var string|int
   */
  protected $testEntityId;

  /**
   * How many events to have been dispatched so far.
   *
   * @var int
   */
  protected $eventsDispatched = 0;

  /**
   * {@inheritdoc}
   */
  public function setUp(): void {
    parent::setUp();
    $this->testEntityType = $this->randomMachineName();
    $this->testEntityId = $this->randomMachineName();

    $this->itemFinder = $this->createMock(XeroSyncItemFinder::class);
    $this->itemManager = $this->createMock(XeroItemManager::class);
    $this->itemManager->expects($this->any())
      ->method('throwErrors')
      ->willReturn($this->itemManager);
    $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class);
    $this->typedDataManager = $this->createMock(TypedDataManagerInterface::class);

    $this->entityA = $this->createMock(EntityInterface::class);
    $this->entityA->expects($this->any())->method('getEntityTypeId')->willReturn($this->testEntityType);
    $this->entityA->expects($this->any())->method('id')->willReturn($this->testEntityId);
    $this->expectNoEntitySave($this->entityA);
    $this->itemA = $this->createMock(XeroComplexItemInterface::class);

    $this->entityB = $this->createMock(EntityInterface::class);
    $this->entityB->expects($this->any())->method('getEntityTypeId')->willReturn($this->testEntityType);
    $this->entityB->expects($this->any())->method('id')->willReturn($this->testEntityId);
    $this->itemB = $this->createMock(XeroComplexItemInterface::class);

    $this->updateItemEvent = $this->createMock(XeroSyncPropertiesEvent::class);
    $this->updateEntityEvent = $this->createMock(XeroSyncPropertiesEvent::class);

    $this->synchronizer = new XeroSyncSynchronizer($this->itemFinder, $this->eventDispatcher, $this->typedDataManager, $this->itemManager);
  }

  /**
   * Expect an item will be found.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity to find the item from.
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   The found item.
   */
  protected function expectItemFoundOnXero(EntityInterface $entity, XeroComplexItemInterface $item) {
    $this->itemFinder->expects($this->once())
      ->method('getItem')
      ->with($entity)
      ->willReturn($item);
  }

  /**
   * Expect an item will be uploaded on Xero.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   The item.
   */
  protected function expectItemUpdatedOnXero(XeroComplexItemInterface $item) {
    $this->itemManager->expects($this->once())
      ->method('updateItem')
      ->with($item)
      ->willReturn($item);
  }

  /**
   * Expect an item to upload to Xero  will return FALSE.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   The item.
   */
  protected function expectItemUpdateOnXeroFailed(XeroComplexItemInterface $item) {
    $this->itemManager->expects($this->once())
      ->method('updateItem')
      ->with($item)
      ->willReturn(FALSE);
  }

  /**
   * Expect no item will be updated on Xero.
   */
  protected function expectItemNotUpdatedOnXero() {
    $this->itemManager->expects($this->never())
      ->method('updateItem');
  }

  /**
   * Expect an item will be created on Xero.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   Expect the item to create.
   */
  protected function expectItemCreatedOnXero(XeroComplexItemInterface $item) {
    $this->itemManager->expects($this->once())
      ->method('createItem')
      ->with($item)
      ->willReturn($item);
  }

  /**
   * Expect an item creation on Xero fails.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   Expect the item to create.
   */
  protected function expectItemCreationOnXeroFailed(XeroComplexItemInterface $item) {
    $this->itemManager->expects($this->once())
      ->method('createItem')
      ->with($item)
      ->willReturn(FALSE);
  }

  /**
   * Expect no item will be created on Xero.
   */
  protected function expectItemNotCreatedOnXero() {
    $this->itemManager->expects($this->never())
      ->method('createItem');
  }

  /**
   * Expect an entity will be saved.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   */
  protected function expectEntitySave(EntityInterface $entity) {
    $entity->expects($this->once())
      ->method('save');
  }

  /**
   * Expect an entity will not be saved.
   *
   * @param \Drupal\Core\Entity\EntityInterface $entity
   *   The entity.
   */
  protected function expectNoEntitySave(EntityInterface $entity) {
    $entity->expects($this->never())
      ->method('save');
  }

  /**
   * Returns a matcher that matches when the method is executed
   * at the given index.
   *
   */
  public static function custom_at(int $index): CustomInvokedAtIndexMatcher
  {
    return new CustomInvokedAtIndexMatcher($index);
  }

  /**
   * Set expectations for an item sync.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface|bool $modifiedItem
   *   The modified item to be returned form the sync event.
   * @param string $eventId
   *   The id of the event to dispatch.
   * @param \Drupal\xero_sync\Event\XeroSyncPropertiesEvent|null $event
   *   (optional) The event object, in case multiple events are needed.
   */
  protected function expectUpdateItemEventDispatched($modifiedItem, $eventId, XeroSyncPropertiesEvent $event = NULL) {
    $event = $event ?? $this->updateItemEvent;
    if ($modifiedItem) {
      $event->expects($this->any())
        ->method('getItem')
        ->willReturn($modifiedItem);
      $event->expects($this->any())
        ->method('isModified')
        ->willReturn(TRUE);
    }
    else {
      $event->expects($this->any())
        ->method('getItem')
        ->willReturn($this->itemA);
      $event->expects($this->any())
        ->method('isModified')
        ->willReturn(FALSE);
    }

    if ($this->eventsDispatched === 0) {
      $this->eventDispatcher->expects($this->custom_at(0))
        ->method('dispatch')
        ->with(
          $this->isInstanceOf(XeroSyncPropertiesEvent::class),
          $this->equalTo('xero_sync.' . $eventId . '.' . $this->testEntityType)
        )
        ->willReturn($event);
    }
    if ($this->eventsDispatched === 1) {
      $dispatch_arguments = [$this->isInstanceOf(XeroSyncPropertiesEvent::class),
        $this->equalTo('xero_sync.' . $eventId . '.' . $this->testEntityType),
      ];

      $this->eventDispatcher->expects($this->exactly(2))
        ->method('dispatch')
        ->willReturnOnConsecutiveCalls($dispatch_arguments, $event);
    }

    $this->eventsDispatched++;
  }

  /**
   * Set expectations for an entity sync.
   *
   * @param \Drupal\Core\Entity\EntityInterface|bool $modifiedEntity
   *   The modified entity to be returned from the sync event.
   * @param string $eventId
   *   The sync event to dispatch.
   */
  protected function expectUpdateEntityEventDispatched($modifiedEntity, $eventId) {
    if ($modifiedEntity) {
      $this->updateEntityEvent->expects($this->any())
        ->method('getEntity')
        ->willReturn($modifiedEntity);
      $this->updateEntityEvent->expects($this->any())
        ->method('isModified')
        ->willReturn(TRUE);
      $this->expectEntitySave($modifiedEntity);
    }
    else {
      $this->updateEntityEvent->expects($this->any())
        ->method('isModified')
        ->willReturn(FALSE);
    }

    if ($this->eventsDispatched === 0) {
      $this->eventDispatcher->expects($this->once())
        ->method('dispatch')
        ->with(
          $this->isInstanceOf(XeroSyncPropertiesEvent::class),
          $this->equalTo('xero_sync.' . $eventId . '.' . $this->testEntityType)
        )
        ->willReturn($this->updateEntityEvent);
    }
    elseif ($this->eventsDispatched === 1) {
      $dispatch_arguments = [$this->isInstanceOf(XeroSyncPropertiesEvent::class),
        $this->equalTo('xero_sync.' . $eventId . '.' . $this->testEntityType),
      ];

      $this->eventDispatcher->expects($this->exactly(2))
        ->method('dispatch')
        ->willReturnOnConsecutiveCalls($dispatch_arguments, $this->updateEntityEvent);
    }

    $this->eventsDispatched++;
  }

  /**
   * Test syncEntityWithItem with default arguments.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncEntityWithItem() {
    $this->expectItemFoundOnXero($this->entityA, $this->itemA);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectUpdateEntityEventDispatched($this->entityB, 'sync_to_entity_from_item');
    $this->expectNoEntitySave($this->entityA);
    $this->synchronizer->syncEntityWithItem($this->entityA);
  }

  /**
   * Test syncEntityWithItem when Xero API fails to update item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncEntityWithItemUnsuccessful() {
    $this->expectItemFoundOnXero($this->entityA, $this->itemA);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdateOnXeroFailed($this->itemB);
    $this->expectUpdateEntityEventDispatched($this->entityB, 'sync_to_entity_from_item');
    $this->expectNoEntitySave($this->entityA);
    $this->expectEntitySave($this->entityB);
    $this->expectException(SyncFailureException::class);
    $this->synchronizer->syncEntityWithItem($this->entityA);
  }

  /**
   * Test syncEntityWithItem when no corresponding item is found.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncEntityWithNoItemFound() {
    $this->itemFinder->expects($this->once())
      ->method('getItem')
      ->with($this->entityA)
      ->willReturn(FALSE);

    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);

    $this->synchronizer->syncEntityWithItem($this->entityA);
  }

  /**
   * Test syncEntityWithItem with the update argument set to FALSE.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncItemUpdateProhibited() {
    $this->expectItemFoundOnXero($this->entityA, $this->itemA);
    $this->expectItemNotUpdatedOnXero();
    $this->expectUpdateEntityEventDispatched($this->entityB, 'sync_to_entity_from_item');
    $this->expectNoEntitySave($this->entityA);
    $this->synchronizer->syncEntityWithItem($this->entityA, NULL, TRUE, FALSE);
  }

  /**
   * Test syncEntityWithItem when the found item does not need update.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncItemNotModifiedByEvent() {
    $this->expectItemFoundOnXero($this->entityA, $this->itemA);
    $this->expectUpdateItemEventDispatched(FALSE, 'sync_to_item_from_entity');
    $this->expectItemNotUpdatedOnXero();
    $this->expectUpdateEntityEventDispatched($this->entityB, 'sync_to_entity_from_item');
    $this->expectNoEntitySave($this->entityA);
    $this->synchronizer->syncEntityWithItem($this->entityA);
  }

  /**
   * Test syncEntityWithItem when the entity does not need updating from item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncEntityNotModifiedByEvent() {
    $this->expectItemFoundOnXero($this->entityA, $this->itemA);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectUpdateEntityEventDispatched(FALSE, 'sync_to_entity_from_item');
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->syncEntityWithItem($this->entityA);
  }

  /**
   * Test the recursion prevention feature of syncEntityWithItem.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::syncEntityWithItem
   */
  public function testSyncRecursion() {
    $this->expectItemFoundOnXero($this->entityA, $this->itemA);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');

    $this->updateEntityEvent->expects($this->once())
      ->method('getEntity')
      ->willReturn($this->entityB);
    $this->updateEntityEvent->expects($this->any())
      ->method('isModified')
      ->willReturn(TRUE);
    $this->expectNoEntitySave($this->entityA);
    $this->expectEntitySave($this->entityB);
    $this->expectItemUpdatedOnXero($this->itemB);

    // Invoke a sync from inside a sync, to see if recursion is successfully
    // prevented.
    $entity = $this->entityB;
    $synchronizer = $this->synchronizer;
    $event = $this->updateEntityEvent;

    $callback = $this->returnCallback(function ($arg1, $arg2) use ($entity, $synchronizer, $event) {
      $synchronizer->syncEntityWithItem($entity);
      return $event;
    });
    $dispatch_arguments = [$this->isInstanceOf(XeroSyncPropertiesEvent::class),
      $this->equalTo('xero_sync.sync_to_entity_from_item.' . $this->testEntityType),
    ];

    $this->eventDispatcher->expects($this->exactly(2))
      ->method('dispatch')
      ->willReturnOnConsecutiveCalls($dispatch_arguments, $callback);

    $this->synchronizer->syncEntityWithItem($this->entityA);
  }

  /**
   * Test unsyncEntityWithItem with an event that modifies the item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::unsyncEntityWithItem
   */
  public function testUnsyncEntityWithItem() {
    $this->itemFinder->expects($this->never())
      ->method('getItem');
    $this->expectUpdateItemEventDispatched($this->itemB, 'unsync_item');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->unsyncEntityWithItem($this->entityA, $this->itemA);
  }

  /**
   * Test unsyncEntityWithItem with an event that does not modify the item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::unsyncEntityWithItem
   */
  public function testUnsyncEntityWithItemNotModifiedByEvent() {
    $this->itemFinder->expects($this->never())
      ->method('getItem');
    $this->expectUpdateItemEventDispatched(FALSE, 'unsync_item');
    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->unsyncEntityWithItem($this->entityA, $this->itemA);
  }

  /**
   * Test createSyncedItem.
   *
   * @todo Find a way of testing this - currently it would need the complex
   * business of mocking the typed data manager to allow the Synchronizer
   * to manufacture a stubbed item, which seems overkill here.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::createSyncedItem
   */
  public function todoTestCreateSyncedItem() {
    $this->expectUpdateItemEventDispatched($this->itemA, 'sync_to_item_from_entity');
    $this->expectItemCreated($this->itemA);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->createSyncedItem($this->entityA, NULL, 'xero_contact');
  }

  /**
   * Test createSyncedItem with a supplied item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::createSyncedItem
   */
  public function testCreateSyncedItemWithItemSupplied() {
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemCreatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->createSyncedItem($this->entityA, $this->itemA);
  }

  /**
   * Test createSyncedItem with a supplied item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::createSyncedItem
   */
  public function testCreateSyncedItemUnsuccessful() {
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemCreationOnXeroFailed($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->expectException(SyncFailureException::class);
    $this->synchronizer->createSyncedItem($this->entityA, $this->itemA);
  }

  /**
   * Test update necessity with an item that needs updating.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedItemNeeded() {
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA);
  }

  /**
   * Test update necessity with an item that does not need updating.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedItemNotNeeded() {
    $this->expectUpdateItemEventDispatched(FALSE, 'sync_to_item_from_entity');
    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA);
  }

  /**
   * Test updating a synced stub without previous state that needs updating.
   *
   * Unless a previous entity state is supplied, it doesn't matter that the
   * item is synced or a stub.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedStubNeeded() {
    $this->itemA->stub = TRUE;
    $this->itemA->synced = TRUE;
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA);
  }

  /**
   * Test updating when a previous state is supplied but not a synced stub.
   *
   * Unless the item is a synced stub, it doesn't matter that a previous state
   * is supplied.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedNeededWithIrrelevantPreviousState() {
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA, FALSE, $this->entityB);
  }

  /**
   * Test updating a synced stub when a previous state is supplied.
   *
   * And the previous state did not modify the item.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedNeededWithRelevantPassivePreviousState() {
    $this->itemA->stub = TRUE;
    $this->itemA->synced = TRUE;
    $previousStateEvent = $this->createMock(XeroSyncPropertiesEvent::class);
    $this->expectUpdateItemEventDispatched(FALSE, 'sync_to_item_from_entity', $previousStateEvent);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA, FALSE, $this->entityB);
  }

  /**
   * Test updating a synced stub when a previous state is supplied.
   *
   * And the previous state did modify the item, but the current state modifies
   * it further.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedNeededWithRelevantActivePreviousState() {
    $this->itemA->stub = TRUE;
    $this->itemA->synced = TRUE;
    $previousStateEvent = $this->createMock(XeroSyncPropertiesEvent::class);
    $this->expectUpdateItemEventDispatched($this->itemA, 'sync_to_item_from_entity', $previousStateEvent);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdatedOnXero($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA, FALSE, $this->entityB);
  }

  /**
   * Test updating a synced stub when a previous state is supplied.
   *
   * And the previous state did modify the item, and the current state does not
   * modify it further.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedNotNeededWithRelevantActivePreviousState() {
    $this->itemA->stub = TRUE;
    $this->itemA->synced = TRUE;
    $previousStateEvent = $this->createMock(XeroSyncPropertiesEvent::class);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity', $previousStateEvent);
    $this->expectUpdateItemEventDispatched(FALSE, 'sync_to_item_from_entity');
    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA, FALSE, $this->entityB);
  }

  /**
   * Test update necessity with an item that does not need updating.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::updateSyncedItemIfNecessary
   */
  public function testUpdateSyncedItemFailed() {
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemUpdateOnXeroFailed($this->itemB);
    $this->expectNoEntitySave($this->entityA);
    $this->expectException(SyncFailureException::class);
    $this->synchronizer->updateSyncedItemIfNecessary($this->entityA, $this->itemA);
  }

  /**
   * Test isItemUpdateNeeded with a simple case of needing update.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::isItemUpdateNeeded
   */
  public function testIsItemUpdateNeededTrue() {
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity');
    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $result = $this->synchronizer->isItemUpdateNeeded($this->entityA, $this->itemA);
    $this->assertTrue($result);
  }

  /**
   * Test isItemUpdateNeeded with a simple case of not needing update.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::isItemUpdateNeeded
   */
  public function testIsItemUpdateNeededFalse() {
    $this->expectUpdateItemEventDispatched(FALSE, 'sync_to_item_from_entity');
    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $result = $this->synchronizer->isItemUpdateNeeded($this->entityA, $this->itemA);
    $this->assertFalse($result);
  }

  /**
   * Test isItemUpdateNeeded when the result is determined by a previous state.
   *
   * @covers \Drupal\xero_sync\XeroSyncSynchronizer::isItemUpdateNeeded
   */
  public function testIsItemUpdateNeededBaseOnPreviousState() {
    $this->itemA->stub = TRUE;
    $this->itemA->synced = TRUE;
    $previousStateEvent = $this->createMock(XeroSyncPropertiesEvent::class);
    $this->expectUpdateItemEventDispatched($this->itemB, 'sync_to_item_from_entity', $previousStateEvent);
    $this->expectUpdateItemEventDispatched(FALSE, 'sync_to_item_from_entity');
    $this->expectItemNotUpdatedOnXero();
    $this->expectNoEntitySave($this->entityA);
    $this->expectNoEntitySave($this->entityB);
    $result = $this->synchronizer->isItemUpdateNeeded($this->entityA, $this->itemA, $this->entityB);
    // The current entity state did not modify the previous item state, so no
    // update needed.
    $this->assertFalse($result);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc