xero_sync-8.x-1.x-dev/modules/xero_sync_user_contact/tests/src/Kernel/QueuingProcessingTest.php

modules/xero_sync_user_contact/tests/src/Kernel/QueuingProcessingTest.php
<?php

namespace Drupal\Tests\xero_sync_user_contact\Kernel;

use Drupal\xero\XeroNullClient;
use Drupal\xero_sync\Plugin\QueueWorker\WorkerBase;
use GuzzleHttp\Exception\RequestException;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\StreamInterface;
use Psr\Log\LoggerInterface;
use Drupal\Core\Logger\RfcLogLevel;
use Radcliffe\Xero\XeroClient;
use GuzzleHttp\Psr7\Response;

/**
 * Test processing queue items.
 *
 * @group xero_sync
 * @group legacy
 */
class QueuingProcessingTest extends XeroSyncUserContactTestBase {

  /**
   * {@inheritdoc}
   */
  protected static $modules = [
    'system',
    'user',
    'serialization',
    'xero',
    'xero_sync',
    'xero_sync_user_contact',
  ];

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $logger;

  /**
   * The Xero client.
   *
   * @var \Radcliffe\Xero\XeroClient|\PHPUnit\Framework\MockObject\MockObject
   */
  protected $xeroClient;

  /**
   * The FQCN of the class responsible for processing queue jobs.
   *
   * @var string
   */
  protected $jobClass = WorkerBase::class;

  /**
   * {@inheritDoc}
   */
  protected function setUp(): void {
    /*
     * The QueuingProcessingTest and AdvancedQueuingProcessingTest
     * should be refactored.
     * It would be better to use ItemManager instead of Xero client for mocking.
     */
    $this->markTestSkipped('The tests should be refactored.');

    parent::setUp();
    $this->disableEntityHandler();
    $this->logger = $this->createMock(LoggerInterface::class);
    $this->xeroClient = $this->getMockBuilder(XeroClient::class)
      ->disableOriginalConstructor()
      ->getMock();

    $this->container->set('logger.channel.xero_sync', $this->logger);
    $this->container->set('xero.client', $this->xeroClient);

    $jobClass = new \ReflectionClass($this->jobClass);
    $waitProperty = $jobClass->getProperty('waitUntil');
    $waitProperty->setAccessible(TRUE);
    $waitProperty->setValue(0);
  }

  /**
   * Test what happens if a job is queued for a user who doesn't exist.
   *
   * Might happen if a user was deleted.
   */
  public function testUserDoesntExist() {
    // No queries, we just log and return quickly.
    $this->logger->expects($this->once())
      ->method('log')
      ->with(
        RfcLogLevel::ERROR,
        $this->stringContains('No valid entity')
      );

    $this->xeroClient->expects($this->never())
      ->method('__call');

    $this->enqueueJob();
    $this->runQueueJob();
  }

  /**
   * Test the creation of a new item if no corresponding item exists.
   */
  public function testContactIsCreatedIfDidntExist() {
    $this->mockLogSuccess();

    $response = $this->getMockResponseForEmpty();
    $this->mockFindByEmail($response);

    $response = $this->getMockResponseFor("{$this->username} ({$this->uid})", $this->email, $this->uid);
    $this->xeroClient->expects($this->at(1))
      ->method('__call')
      ->with('put')
      ->willReturn($response);

    $this->assertUserCreation(TRUE);
  }

  /**
   * Test if an existing item is found that corresponds to a new user.
   *
   * The item should be referenced from the user, but not updated if there is no
   * new information on it that needs updating.
   */
  public function testContactIsReferencedIfFoundByEmail() {
    $this->mockLogSuccess();

    // Contact number is already set so there will be no need to update that.
    $response = $this->getMockResponseFor("{$this->username} ({$this->uid})", $this->email, $this->uid);
    $this->mockFindByEmail($response);

    $this->assertUserCreation(TRUE);
  }

  /**
   * Test if an existing item is found that corresponds to a new user.
   *
   * The item should be updated and referenced from the user.
   */
  public function testContactIsUpdatedIfFoundByEmail() {
    $this->mockLogSuccess();

    // Contact number is not set so updating will be needed.
    $response = $this->getMockResponseFor("{$this->username} ({$this->uid})", $this->email);
    $this->mockFindByEmail($response);

    $postResponse = $this->getMockResponseFor("{$this->username} ({$this->uid})", $this->email, $this->uid);
    $this->xeroClient->expects($this->at(1))
      ->method('__call')
      ->with('post')
      ->willReturn($postResponse);

    $this->assertUserCreation(TRUE);
  }

  /**
   * Test an empty response when creating.
   */
  public function testEmptyResponseWhenCreating() {
    $this->mockLogException();

    $response = $this->getMockResponseForEmpty();
    $this->mockFindByEmail($response);

    // An empty response to creation.
    $this->xeroClient->expects($this->at(1))
      ->method('__call')
      ->with('put')
      ->willReturn($response);

    $this->assertUserCreation(FALSE);
  }

  /**
   * Test a bad response when finding.
   */
  public function testBadResponseWhenFinding() {
    $this->mockLogException();

    $this->mockFindByEmail($this->getErrorResponse(500));

    $this->assertUserCreation(FALSE);
  }

  /**
   * Test a bad response when creating.
   */
  public function testBadResponseWhenCreating() {
    $this->mockLogException();

    $response = $this->getMockResponseForEmpty();
    $this->mockFindByEmail($response);

    $this->xeroClient->expects($this->at(1))
      ->method('__call')
      ->with('put')
      ->willReturn($this->getErrorResponse(500));

    $this->assertUserCreation(FALSE);
  }

  /**
   * Test an API error when finding.
   */
  public function testApiErrorWhenFinding() {
    $this->mockLogException();

    $this->xeroClient->expects($this->at(0))
      ->method('__call')
      ->with('get')
      ->willThrowException($this->getRequestException(500));

    $this->assertUserCreation(FALSE);
  }

  /**
   * Test throwing an exception when creating.
   */
  public function testApiErrorWhenCreating() {
    $this->mockLogException();

    $response = $this->getMockResponseForEmpty();
    $this->mockFindByEmail($response);

    $this->xeroClient->expects($this->at(1))
      ->method('__call')
      ->with('put')
      ->willThrowException($this->getRequestException(500));

    $this->assertUserCreation(FALSE);
  }

  /**
   * Test an API error when finding.
   */
  public function testInvalidClientWhenFinding() {
    $this->logger->expects($this->once())
      ->method('log')
      ->with(
        RfcLogLevel::ERROR,
        $this->stringContains('Xero client unavailable when trying to process job')
      );
    $this->xeroClient = $this->createMock(XeroNullClient::class);
    $this->container->set('xero.client', $this->xeroClient);
    $this->assertUserCreation(FALSE);
  }

  /**
   * Test the API rate limit being exceeded when finding.
   */
  public function testMinuteRateLimitExceededWhenFinding() {
    $this->logger->expects($this->at(0))
      ->method('log')
      ->with(
        RfcLogLevel::WARNING,
        $this->stringContains('Xero rate limit exceeded while processing xero_sync_sync queue, retry in 5 seconds')
      );
    $this->logger->expects($this->at(1))
      ->method('log')
      ->with(
        RfcLogLevel::INFO,
        $this->stringContains('Success in queue')
      );

    $this->xeroClient->expects($this->at(0))
      ->method('__call')
      ->with('get')
      ->willThrowException($this->getRequestException(429, '5'));

    // The job should be retried after a 5 second delay.
    $response = $this->getMockResponseFor("{$this->username} ({$this->uid})", $this->email, $this->uid);
    $this->xeroClient->expects($this->at(1))
      ->method('__call')
      ->with('get')
      ->willReturn($response);

    $this->expectEntityUpdates(1);
    $this->user->save();

    $start = time();
    $this->enqueueJob();
    $this->runQueueJob();
    $this->runQueueJob();
    $now = time();
    // $this->assertTrue( $now > $start + 4, "The job retry should have paused for 5 seconds. Start:$start Now:$now");
    // $this->assertItemReferencedInField($this->user, 'xero_contact', $this->xeroId);
  }

  /**
   * Test the API rate limit being exceeded when finding.
   */
  public function testHourRateLimitExceededWhenFinding() {
    $this->logger->expects($this->once())
      ->method('log')
      ->with(
        RfcLogLevel::WARNING,
        $this->stringContains('Xero rate limit exceeded while processing xero_sync_sync queue, retry in 65 seconds')
      );

    $this->xeroClient->expects($this->once())
      ->method('__call')
      ->with('get')
      ->willThrowException($this->getRequestException(429, '65'));

    // The job will not be retried. We should test this better.
    $this->assertUserCreation(FALSE);

    $this->assertNoItemReferencedInField($this->user);
  }

  /**
   * Mock finding an item by email.
   *
   * @param string $response
   *   The response to expect. Not sure what data type should be.
   */
  protected function mockFindByEmail($response) {
    $this->xeroClient->expects($this->at(0))
      ->method('__call')
      ->with('get')
      ->willReturn($response);
  }

  /**
   * Expect success to be logged.
   */
  protected function mockLogSuccess() {
    $this->logger->expects($this->once())
      ->method('log')
      ->with(
        RfcLogLevel::INFO,
        $this->stringContains('Success in queue')
      );
  }

  /**
   * Expect an exception to be logged.
   */
  protected function mockLogException($count = 1) {
    $this->logger->expects($this->exactly($count))
      ->method('log')
      ->with(
        RfcLogLevel::ERROR,
        $this->stringContains('Exception in queue')
      );
  }

  /**
   * Get a mock request exception.
   *
   * @param int $code
   *   The response status code.
   * @param string $retryAfter
   *   (optional) How many seconds to retry after, a response header.
   *
   * @return \GuzzleHttp\Exception\RequestException
   *   A request exception with mock request and response.
   */
  protected function getRequestException($code, $retryAfter = NULL) {
    $mockRequest = $this->createMock(RequestInterface::class);
    $mockRequest->expects($this->any())
      ->method('getBody')
      ->willReturn($this->getBody());
    $mockResponse = $this->getErrorResponse($code);
    if ($retryAfter) {
      $mockResponse->expects($this->any())
        ->method('hasHeader')
        ->with('Retry-After')
        ->willReturn(TRUE);
      $mockResponse->expects($this->any())
        ->method('getHeader')
        ->with('Retry-After')
        ->willReturn([$retryAfter]);
    }
    $e = new RequestException('Xero API error', $mockRequest, $mockResponse);
    return $e;
  }

  /**
   * Get a mock response for an error.
   *
   * @param int $code
   *   An http status code.
   *
   * @return \PHPUnit\Framework\MockObject\MockObject
   *   A mock response.
   */
  protected function getErrorResponse($code) {
    $mockResponse = $this->createMock(ResponseInterface::class);
    $mockResponse->expects($this->any())
      ->method('getStatusCode')
      ->willReturn($code);
    $mockResponse->expects($this->any())
      ->method('getBody')
      ->willReturn($this->getBody());
    return $mockResponse;
  }

  /**
   * Get the find by email options.
   */
  protected function getFindByEmailOptions() {
    NULL;
    // @todo re-enable this once https://www.drupal.org/project/xero/issues/3156893
    // is committed and the Xero headers stabilise.
    // ['Contacts', ['query' => ['where' => 'EmailAddress=="' . $this->email . '"']]];
  }

  /**
   * Get an unexpected response object.
   *
   * @return \PHPUnit\Framework\MockObject\MockObject
   *   A mock response object.
   */
  protected function getBody() {
    $body = $this->createMock(StreamInterface::class);
    $body->expects($this->any())
      ->method('getContents')
      ->willReturn("Maybe the Xero API is behaving strangely today");
    return $body;
  }

  /**
   * Get a block of Xml representing an empty Xero response.
   */
  protected function getMockResponseForEmpty() {
    $xml = '<Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Id>1040dd4d-e6bd-4430-9844-6345fb214f38</Id>
  <Status>OK</Status>
  <ProviderName>Drupal Xero</ProviderName>
  <DateTimeUTC>2018-09-27T03:18:43.3794355Z</DateTimeUTC>
  </Response>';

    $response = new Response(
      200,
      [
        'Content-Type' => 'text/xml',
      ],
      $xml
    );

    return $response;
  }

  /**
   * Get a block of xml representing a successful Xero response.
   */
  protected function getMockResponseFor($name = '', $mail = '', $contactNumber = '') {
    $xml = '<Response xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Id>1040dd4d-e6bd-4430-9844-6345fb214f38</Id>
  <Status>OK</Status>
  <ProviderName>Drupal Xero</ProviderName>
  <DateTimeUTC>2018-09-27T03:18:43.3794355Z</DateTimeUTC>
  <Contacts>
    <Contact>
      <ContactID>' . $this->xeroId . '</ContactID>
      <ContactNumber>' . $contactNumber . '</ContactNumber>
      <ContactStatus>ACTIVE</ContactStatus>
      <Name>' . $name . '</Name>
      <FirstName></FirstName>
      <LastName></LastName>
      <EmailAddress>' . $mail . '</EmailAddress>
      <Addresses>
        <Address>
          <AddressType>POBOX</AddressType>
        </Address>
        <Address>
          <AddressType>STREET</AddressType>
        </Address>
      </Addresses>
      <Phones>
        <Phone>
          <PhoneType>DDI</PhoneType>
        </Phone>
        <Phone>
          <PhoneType>DEFAULT</PhoneType>
        </Phone>
        <Phone>
          <PhoneType>FAX</PhoneType>
        </Phone>
        <Phone>
          <PhoneType>MOBILE</PhoneType>
        </Phone>
      </Phones>
      <UpdatedDateUTC>2018-09-20T14:51:53.56</UpdatedDateUTC>
      <IsSupplier>false</IsSupplier>
      <IsCustomer>false</IsCustomer>
      <HasAttachments>false</HasAttachments>
    </Contact>
  </Contacts>
  </Response>';

    $response = new Response(
      200,
      [
        'Content-Type' => 'text/xml',
      ],
      $xml
    );

    return $response;
  }

  /**
   * Create a user, run the queue, and perhaps assert that an item is stored.
   *
   * @param bool $referenced
   *   Whether or not to expect an item to be referenced on the user.
   *
   * @throws \Drupal\Core\Entity\EntityStorageException
   */
  protected function assertUserCreation($referenced) {
    $this->expectEntityUpdates((int) $referenced);
    $this->user->save();
    $this->enqueueJob();
    $this->runQueueJob();
    if ($referenced) {
      $this->assertItemReferencedInField($this->user, 'xero_contact', $this->xeroId);
    }
    else {
      $this->assertNoItemReferencedInField($this->user);
    }
  }

  /**
   * Create the queue job.
   */
  protected function enqueueJob() {}

  /**
   * Process the queue job.
   *
   * @var int $repeat
   *   How many times to process.
   */
  protected function runQueueJob($repeat = 1) {
    $queue_name = 'xero_sync_sync';
    /** @var \Drupal\Core\Queue\QueueWorkerInterface $queue_worker */
    $queue_worker = \Drupal::service('plugin.manager.queue_worker')
      ->createInstance($queue_name);
    for ($k = 0; $k < $repeat; $k++) {
      $queue_worker->processItem(
        [
          'entity_id' => $this->uid,
          'entity_type' => 'user',
        ]);
    }
  }

}

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

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