xero_sync-8.x-1.x-dev/tests/src/Kernel/XeroSyncTestBase.php

tests/src/Kernel/XeroSyncTestBase.php
<?php

namespace Drupal\Tests\xero_sync\Kernel;

use Drupal\Component\Render\FormattableMarkup;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
use Drupal\Core\Entity\FieldableEntityInterface;
use Drupal\xero\Normalizer\XeroNormalizer;
use Drupal\xero\TypedData\XeroComplexItemInterface;
use Drupal\xero_sync\XeroSyncEntityHandler;
use Radcliffe\Xero\XeroClient;

/**
 * A base class for testing that uses the mock itemManager instead of Xero API.
 *
 * @group xero_sync
 */
abstract class XeroSyncTestBase extends EntityKernelTestBase {

  /**
   * The calls the mock itemManager should receive.
   *
   * @var array
   */
  protected $expectedItemManagerCalls = [];

  /**
   * {@inheritDoc}
   */
  protected function setUp(): void {
    parent::setUp();
    // Any calls that reach the Xero client will fail with an obscure
    // container error because it is not configured, so mock it.
    $client = $this->createMock(XeroClient::class);
    $this->container->set('xero.client', $client);
  }

  /**
   * Set up the mock itemManager.
   *
   * @param array $expectations
   *   The calls the itemManager expects to get, & the values it should return.
   *   The first array value is the itemManager method name, the second is an
   *   array of arguments for the method, and third is the value to return.
   */
  protected function itemManagerExpects(array $expectations) {
    $returns = [];
    $this->expectedItemManagerCalls = [];
    foreach ($expectations as $expectation) {
      $this->expectedItemManagerCalls[] = [
        'method' => $expectation['method'],
        'arguments' => $expectation['arguments'],
      ];
      $returns[] = $expectation['return'];
    }
    $this->getItemManager()->setReturns($returns);
  }

  /**
   * Get the item manager, if not already mocked and set.
   *
   * @return \Drupal\xero\XeroItemManagerInterface
   *   The item manager.
   */
  protected function getItemManager() {
    if (!isset($this->itemManager)) {
      $this->itemManager = \Drupal::service('xero.item_manager');
    }
    return $this->itemManager;
  }

  /**
   * Assert that the mock item manager was called in the expected ways.
   */
  protected function assertItemManagerExpectationsMet() {
    $this->assertNotNull($this->expectedItemManagerCalls);
    $calls = $this->getItemManager()->getCalls();
    $message = (string) new FormattableMarkup("itemManager was not called the expected number of times.  \n Actual: \n @actual \n \n Expected: \n @expected", [
      '@actual' => print_r(array_column($calls, 'method'), TRUE),
      '@expected' => print_r(array_column($this->expectedItemManagerCalls, 'method'), TRUE),
    ]);
    $this->assertEquals(count($this->expectedItemManagerCalls), count($calls), $message);

    for ($i = 0; $i < count($calls); $i++) {
      $this->assertEquals($this->expectedItemManagerCalls[$i]['method'], $calls[$i]['method'], "Call to unexpected method on itemManager.");
      $actualArgs = $calls[$i]['arguments'];
      $expectedArgs = $this->expectedItemManagerCalls[$i]['arguments'];
      $this->assertEquals(count($expectedArgs), count($actualArgs), "itemManager invocation " . $i . " (" . $calls[$i]['method'] . ") has unexpected number of arguments");

      // Provide special comparison for any arguments that are Xero items,
      // to help debugging by providing better visibility of item properties.
      foreach ($actualArgs as $index => $actualArg) {
        $expectedArg = $expectedArgs[$index];
        $expectedIsXeroItem = $expectedArg instanceof XeroComplexItemInterface;
        $actualIsXeroItem = $actualArg instanceof XeroComplexItemInterface;
        $this->assertFalse(
          (($expectedIsXeroItem && !$actualIsXeroItem) ||
            (!$expectedIsXeroItem && $actualIsXeroItem)),
          "If actual or expected is a Xero item, then the other should be too"
        );
        if ($expectedIsXeroItem && $actualIsXeroItem) {
          $message = (string) new FormattableMarkup(
            "Mock query manager call @i to '@method' method contained a Xero item in its @index argument.",
            [
              '@i' => $i + 1,
              '@method' => $calls[$i]['method'],
              '@index' => $index + 1,
            ]);
          $this->assertEqualXeroItems($expectedArg, $actualArg, $message);
          $expectedArgs[$index] = "Xero item";
          $actualArgs[$index] = "Xero item";
        }
      }

      $this->assertEquals($expectedArgs, $actualArgs, "Unexpected arguments to '" . $calls[$i]['method'] . "' method on itemManager.");
    }
  }

  /**
   * Assert that two Xero items are equal, with helpful messages if not.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $expected
   *   The expected item.
   * @param mixed $actual
   *   The actual item.
   * @param string|null $message
   *   (optional) An additional message to output if the items are not equal.
   */
  protected function assertEqualXeroItems(XeroComplexItemInterface $expected, $actual, $message = NULL) {
    $this->assertInstanceOf(XeroComplexItemInterface::class, $actual, "Actual item should be a Xero typed data object");
    $this->assertEquals($expected->getPluginId(), $actual->getPluginId(), "Items should use same Xero type plugin");

    $normalizer = new XeroNormalizer(\Drupal::service('typed_data_manager'));
    $normalizer->setSerializer(\Drupal::service('serializer'));
    $message = $message . " The Xero item different to expected.";
    $context = [
      'plugin_id' => $expected->getPluginId(),
    ];
    $actualNormalized = $normalizer->normalize($actual, 'xml', $context);
    $expectedNormalized = $normalizer->normalize($expected, 'xml', $context);
    $message = (string) new FormattableMarkup("@message  \n Actual: \n @actual \n \n Expected: \n @expected", [
      '@message' => $message,
      '@actual' => print_r($actualNormalized, TRUE),
      '@expected' => print_r($expectedNormalized, TRUE),
    ]);
    $this->assertEquals($expectedNormalized, $actualNormalized, $message);
  }

  /**
   * Convert a typed data Xero item into an array of strings.
   *
   * @param \Drupal\xero\TypedData\XeroComplexItemInterface $item
   *   The Xero item.
   *
   * @return array
   *   The array of strings.
   */
  protected function simplifyXeroItem(XeroComplexItemInterface $item) {
    $strings = [];
    foreach ($item->getSpecifiedProperties() as $name => $property) {
      $string = $property->getString();
      if (!empty($string)) {
        $strings[$name] = $string;
      }
    }
    return $strings;
  }

  /**
   * Build a typed data object representing a Xero item.
   *
   * @param string $type
   *   The Xero type.
   * @param array $properties
   *   The item's initial properties.
   *
   * @return \Drupal\Core\TypedData\TypedDataInterface
   *   The typed data object.
   */
  protected function buildXeroItem($type, array $properties) {
    $definition = \Drupal::typedDataManager()->createDataDefinition($type);
    $item = \Drupal::typedDataManager()->create($definition, $properties);
    $this->assertInstanceOf(XeroComplexItemInterface::class, $item);
    return $item;
  }

  /**
   * Set up a Xero reference field on an entity type.
   *
   * @param string $entityType
   *   The entity type.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function setUpXeroField($entityType) {
    $this->entityHandler = \Drupal::service('xero_sync.entity_handler');

    $this->entityTypeManager->clearCachedDefinitions();
    $this->installEntitySchema($entityType);

    /** @var \Drupal\field\Entity\FieldStorageConfig $field_storage */
    FieldStorageConfig::create([
      'field_name' => $this->fieldName,
      'type' => 'xero_reference',
      'entity_type' => $entityType,
      'cardinality' => 1,
    ])->save();

    FieldConfig::create([
      'entity_type' => $entityType,
      'field_name' => $this->fieldName,
      'bundle' => $entityType,
      'label' => 'Test Xero field',
    ])->save();

    $this->entityTypeManager->clearCachedDefinitions();
    $entityFieldManager = $this->container->get('entity_field.manager');
    $definitions = $entityFieldManager->getFieldStorageDefinitions($entityType);
    $this->assertTrue(!empty($definitions[$this->fieldName]));
  }

  /**
   * Assert that a specific item is referenced in an entity's Xero Sync field.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity.
   * @param string $expectedType
   *   The Xero type of the expected item.
   * @param string $expectedId
   *   The guid of the expected item.
   */
  protected function assertItemReferencedInField(FieldableEntityInterface $entity, $expectedType, $expectedId) {
    $entity = $this->reloadEntity($entity);
    $field = $entity->get(XeroSyncEntityHandler::DEFAULT_FIELD_NAME);
    $this->assertFalse($field->isEmpty(), "Field should not be empty.");
    $storedId = $field->first()->guid;
    $storedType = $field->first()->type;
    $this->assertEquals($storedType, $expectedType);
    $this->assertEquals($storedId, $expectedId);
  }

  /**
   * Assert that there is no item referenced in an entity's Xero Sync field.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity.
   */
  protected function assertNoItemReferencedInField(FieldableEntityInterface $entity) {
    $entity = $this->reloadEntity($entity);
    $field = $entity->get(XeroSyncEntityHandler::DEFAULT_FIELD_NAME);
    $this->assertTrue($field->isEmpty());
  }

  /**
   * Store a value in the Xero Sync field on an entity.
   *
   * @param \Drupal\Core\Entity\FieldableEntityInterface $entity
   *   The entity.
   * @param string $type
   *   The Xero type.
   * @param string $id
   *   The Xero guid.
   */
  protected function setXeroField(FieldableEntityInterface &$entity, $type, $id) {
    $entity->set(XeroSyncEntityHandler::DEFAULT_FIELD_NAME, [
      'guid' => $id,
      'type' => $type,
    ]);
  }

  /**
   * Creates an entity_test entity and stores a contact id on it.
   *
   * @param string $contactGuid
   *   The guid to be stored in the field.
   *
   * @return \Drupal\Core\Entity\ContentEntityInterface
   *   The created entity.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function createEntityWithContact($contactGuid) {
    $entity = EntityTest::create([
      $this->fieldName => [
        'type' => 'xero_contact',
        'guid' => $contactGuid,
      ],
    ]);
    $entity->save();
    $this->assertItemReferencedInField($entity, 'xero_contact', $contactGuid);
    return $entity;
  }

  /**
   * Create a Guid with or without curly braces.
   *
   * @param bool|null $braces
   *   (Optional) Return Guid wrapped in curly braces.
   *
   * @return string
   *   Guid string.
   */
  protected function createGuid($braces = TRUE) {
    $hash = strtolower(hash('ripemd128', md5($this->getRandomGenerator()->string(100))));
    $guid = substr($hash, 0, 8) . '-' . substr($hash, 8, 4) . '-' . substr($hash, 12, 4);
    $guid .= '-' . substr($hash, 16, 4) . '-' . substr($hash, 20, 12);

    if ($braces) {
      return '{' . $guid . '}';
    }

    return $guid;
  }

  /**
   * Replace Xero's item manager with a test version that keeps track of calls.
   *
   * Possibly this could be replaced by a standard PHPUnit mock.
   */
  protected function mockItemManager() {
    $typed_data_manager = \Drupal::service('typed_data_manager');
    $factory = \Drupal::service('xero.query.factory');
    $logger = \Drupal::service('logger.factory');
    $item_manager = new MockItemManager($typed_data_manager, $factory, $logger);
    $this->container->set('xero.item_manager', $item_manager);
  }

  /**
   * Disable the Xero Sync entity handler.
   *
   * As entity CRUD hooks typically invoke the entity handler to initiate
   * syncing this allows us to setup tests that entities without needing to
   * consider the effect of hooks on syncing.
   */
  protected function disableEntityHandler() {
    $this->entityHandler = $this->createMock(XeroSyncEntityHandler::class);
    $this->entityHandler->expects($this->any())
      ->method('getFieldName')
      ->willReturn(XeroSyncEntityHandler::DEFAULT_FIELD_NAME);
    $this->container->set('xero_sync.entity_handler', $this->entityHandler);
  }

  /**
   * Set an expectation that there will not be further entity updates.
   *
   * @param int $count
   *   How many entity updates to expect.
   */
  protected function expectEntityUpdates($count) {
    $this->entityHandler->expects($this->exactly($count))
      ->method('handleUpdate');
  }

  /**
   * Disable the use of queues, so that syncing happens immediately.
   */
  protected function bypassQueues() {
    $logger = \Drupal::service('logger.channel.xero_sync');
    $entity_type_manager = \Drupal::service('entity_type.manager');
    $entity_type_bundle_info = \Drupal::service('entity_type.bundle.info');
    $synchronizer = \Drupal::service('xero_sync.synchronizer');
    $typed_data_manager = \Drupal::service('typed_data_manager');
    $job_manager = \Drupal::service('xero_sync.job_manager');
    $serializer = \Drupal::service('serializer');
    $time = \Drupal::service('datetime.time');
    $entity_handler = \Drupal::service('xero_sync.entity_handler');
    $queueless = new QueuelessJobManager($logger, $entity_type_manager, $entity_type_bundle_info, $synchronizer, $typed_data_manager, $job_manager, $serializer, $time, $entity_handler);
    $this->container->set('xero_sync.job_manager', $queueless);

    // We have to reset the entity handler also, because the job manager is a
    // dependency of it and it may already be instantiated.
    $entity_handler = new XeroSyncEntityHandler($queueless, $synchronizer, $typed_data_manager, $entity_type_manager, \Drupal::service('event_dispatcher'));
    $this->container->set('xero_sync.entity_handler', $entity_handler);
  }

}

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

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