xero-8.x-2.x-dev/tests/src/Kernel/XeroItemManagerTest.php
tests/src/Kernel/XeroItemManagerTest.php
<?php
namespace Drupal\Tests\xero\Kernel;
use Drupal\KernelTests\KernelTestBase;
use Drupal\xero\TypedData\XeroComplexItemInterface;
use Drupal\xero\XeroItemManager;
use Drupal\xero\XeroQuery;
use Drupal\xero\XeroQueryFactory;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTrait;
/**
* Tests the Xero item manager.
*
* @coversDefaultClass \Drupal\xero\XeroItemManager
* @group xero
*/
class XeroItemManagerTest extends KernelTestBase {
use ProphecyTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'serialization',
'user',
'xero',
];
/**
* The Xero Item Manager.
*
* @var \Drupal\xero\XeroItemManager
*/
protected $itemManager;
/**
* The Xero query factory.
*
* @var \Drupal\xero\XeroQueryFactory
*/
protected $xeroQueryFactory;
/**
* The ContactID of the test contact.
*
* @var string
*/
protected $xeroId = 'bd2270c3-8706-4c11-9cfb-000b551c3f51';
/**
* {@inheritdoc}
*/
public function setUp(): void {
parent::setUp();
$this->xeroQueryFactory = $this->createMock(XeroQueryFactory::class);
$typedDataManager = \Drupal::service('typed_data_manager');
$loggerProphet = $this->prophesize('\Drupal\Core\Logger\LoggerChannelInterface');
$loggerProphet->error(Argument::any(), Argument::any());
$loggerFactoryProphet = $this->prophesize('\Drupal\Core\Logger\LoggerChannelFactoryInterface');
$loggerFactoryProphet->get('xero')->willReturn($loggerProphet->reveal());
$this->itemManager = new XeroItemManager($typedDataManager, $this->xeroQueryFactory, $loggerFactoryProphet->reveal());
}
/**
* Test the createItem and updateItem methods.
*
* @param string $call
* The method to call.
* @param string $method
* The HTTP method to use.
* @param string $type
* The xero type to create.
* @param mixed $value
* The value to set on the data type.
* @param array|bool $expected
* The expected return class, instance, or FALSE.
*
* @dataProvider sendItemProvider
*
* @covers \Drupal\xero\XeroItemManager::createItem
* @covers \Drupal\xero\XeroItemManager::updateItem
*/
public function testSendItem($call, $method, $type, $value, $expected) {
$item = $this->buildXeroItem('xero_contact', $value);
$result = $expected ? $this->buildXeroItem($type, $expected) : $expected;
$expectations = [
['method' => $method, 'item' => $item, 'result' => $result],
];
$this->setSendQueryExpectations($expectations);
$this->assertEquals($this->itemManager->{$call}($item), $result);
}
/**
* Data provider for testSendItem() method.
*
* @return array[]
* Test scenarios.
*/
public static function sendItemProvider() {
return [
[
'call' => 'createItem',
'method' => 'put',
'type' => 'xero_contact',
'value' => [
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
],
'expected' => [
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
[
'call' => 'createItem',
'method' => 'put',
'type' => 'xero_contact',
'value' => [
'EmailAddress' => 'test@example.com',
],
'expected' => FALSE,
],
[
'call' => 'updateItem',
'method' => 'post',
'type' => 'xero_contact',
'value' => [
'Name' => 'XYZ Company',
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
],
'expected' => [
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'XYZ Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
[
'call' => 'updateItem',
'method' => 'post',
'type' => 'xero_contact',
'value' => [
'EmailAddress' => 'test@example.com',
],
'expected' => FALSE,
],
];
}
/**
* Test the reloadItem method.
*
* @param string $type
* The xero type to build.
* @param array|bool $value
* The data to set.
* @param string|bool $expected
* The expected class or FALSE.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
* @throws \Throwable
*
* @dataProvider loadItemProvider
*
* @covers \Drupal\xero\XeroItemManager::loadItem
*/
public function testLoadItem($type, $value, $expected) {
$this->buildLoadExpectations($type, $value);
$actual = $this->itemManager->loadItem('xero_contact', $this->xeroId);
if ($expected) {
$this->assertInstanceOf($expected, $actual);
}
else {
$this->assertFalse($actual);
}
}
/**
* Test the reloadItem method.
*
* @param string $type
* The xero type to build.
* @param array|bool $value
* The data to set.
* @param string|bool $expected
* The expected class or FALSE.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
* @throws \Throwable
*
* @dataProvider loadItemProvider
*
* @covers \Drupal\xero\XeroItemManager::reloadItem
*/
public function testReloadItem($type, $value, $expected) {
$this->buildLoadExpectations($type, $value);
$item = $this->buildXeroItem($type, $value);
$actual = $this->itemManager->reloadItem($item);
if ($expected) {
$this->assertInstanceOf($expected, $actual);
}
else {
$this->assertFalse($actual);
}
}
/**
* Helper method to build expectations for load tests.
*
* @param string $type
* The xero type.
* @param array|bool $value
* The value to set on the type.
*/
public function buildLoadExpectations($type, $value) {
$result = $value ? $this->buildXeroItem($type, $value) : FALSE;
$query = $this->buildMockQuery($type, $result);
$definition = \Drupal::typedDataManager()->createDataDefinition($type);
$class = $definition->getClass();
$query->expects($this->once())
->method('setId')
->with($this->xeroId)
->willReturn($query);
$this->xeroQueryFactory->expects($this->once())
->method('get')
->willReturn($query);
}
/**
* Data provider for testLoadItem() and testReloadItem() methods.
*
* @return array[]
* Test scenarios.
*/
public static function loadItemProvider() {
return [
[
'type' => 'xero_contact',
'value' => [
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
'expected' => '\Drupal\xero\Plugin\DataType\Contact',
],
[
'type' => 'xero_contact',
'value' => [],
'expected' => FALSE,
],
];
}
/**
* Test the findItem method.
*
* @param array $conditions
* The query conditions to test.
* @param bool|null $unique
* The unique parameter.
* @param string $type
* The xero data type.
* @param array|bool|null $value
* The value to set the data.
* @param string|bool $expected
* Success indicator to assert.
*
* @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
* @throws \Drupal\Core\TypedData\Exception\MissingDataException
*
* @dataProvider findItemProvider
*
* @covers \Drupal\xero\XeroItemManager::findItem
*/
public function testFindItem(array $conditions, $unique, $type, $value, $expected) {
$results = $value;
if ($value) {
$results = [];
foreach ($value as $i => $result) {
$results[] = $this->buildXeroItem($type, $result);
}
}
$query = $this->buildMockQuery($type, $results);
// Do we really need to test conditions because PHPUnit is making it hard.
$query->method('addCondition')->willReturn($query);
$this->xeroQueryFactory
->expects($this->once())
->method('get')
->willReturn($query);
$actual = $this->itemManager->findItem('xero_contact', $conditions, $unique);
if ($expected) {
$this->assertInstanceOf($expected, $actual);
}
else {
$this->assertEquals($expected, $actual);
}
}
/**
* Data provider for testFindItem() method.
*
* @return array<string,mixed>[]
* Test scenarios.
*/
public static function findItemProvider() {
return [
'no result for unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => TRUE,
'type' => 'xero_contact',
'value' => FALSE,
'expected' => FALSE,
],
'no result for not unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => FALSE,
'type' => 'xero_contact',
'value' => FALSE,
'expected' => FALSE,
],
'null result for unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => TRUE,
'type' => 'xero_contact',
'value' => NULL,
'expected' => NULL,
],
'null result for not unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => FALSE,
'type' => 'xero_contact',
'value' => NULL,
'expected' => NULL,
],
'result for unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => TRUE,
'type' => 'xero_contact',
'value' => [
[
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
'expected' => '\Drupal\xero\Plugin\DataType\Contact',
],
'result for not unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => FALSE,
'type' => 'xero_contact',
'value' => [
[
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
'expected' => '\Drupal\xero\Plugin\DataType\Contact',
],
'multiple results for unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => TRUE,
'type' => 'xero_contact',
'value' => [
[
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
[
'ContactID' => 'ebd06280-af70-4bed-97c6-7451a454ad85',
'ContactNumber' => '67890',
'FirstName' => 'Sam',
'LastName' => 'Gutierrez',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
'expected' => FALSE,
],
'multiple results for not unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
],
'unique' => FALSE,
'type' => 'xero_contact',
'value' => [
[
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
[
'ContactID' => 'ebd06280-af70-4bed-97c6-7451a454ad85',
'ContactNumber' => '67890',
'FirstName' => 'Sam',
'LastName' => 'Gutierrez',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
'expected' => '\Drupal\xero\Plugin\DataType\Contact',
],
'result with multiple conditions without unique' => [
'conditions' => [
['EmailAddress', 'test@example.com', '=='],
['ContactNumber', '5', 'NOT NULL'],
],
'unique' => NULL,
'type' => 'xero_contact',
'value' => [
[
'ContactID' => 'bd2270c3-8706-4c11-9cfb-000b551c3f51',
'ContactNumber' => '12345',
'Name' => 'ABC Company',
'EmailAddress' => 'test@example.com',
'Status' => 'ACTIVE',
'UpdatedDateUTC' => '2020-01-01T00:00:00.000',
],
],
'expected' => '\Drupal\xero\Plugin\DataType\Contact',
],
];
}
/**
* Setup expectations about the query used to send data to Xero.
*
* @param array $expectations
* An array of expectations, one per expected query.
*/
protected function setSendQueryExpectations(array $expectations) {
$willReturn = [];
foreach ($expectations as $index => $expectation) {
if (!isset($expectation['type'])) {
$expectation['type'] = 'xero_contact';
}
$query = $this->buildMockQuery(
$expectation['type'],
$expectation['result'],
$expectation['method'],
$expectation['item']
);
$willReturn[] = $query;
}
$this->xeroQueryFactory
->method('get')
->willReturnOnConsecutiveCalls(...$willReturn);
}
/**
* Builds a mock Xero query object, with expectations.
*
* @param string $type
* The typed to be expected to be set on the query.
* @param mixed $result
* The result the query is expected to return. Result values have these
* meanings:
* - FALSE means the query returns FALSE.
* - NULL means the query returns an empty results list.
* - an item means the query returns a list with the item as first value.
* - an array of items means the query returns a list with those items.
* @param string|null $method
* The method to be expected to be set on the query.
* @param \Drupal\xero\TypedData\XeroComplexItemInterface|null $item
* The item to expect to be set as the query's data.
*
* @return \PHPUnit\Framework\MockObject\MockObject
* A mock Xero query.
*/
protected function buildMockQuery($type, $result, $method = NULL, $item = NULL) {
if ($result !== FALSE) {
$resultsProphet = $this->prophesize('\Drupal\xero\Plugin\DataType\XeroItemList');
if (is_null($result)) {
$resultsProphet->count()->willReturn(0);
}
else {
if (!is_array($result)) {
$resultItems = [$result];
}
else {
$resultItems = $result;
}
$resultsProphet->count()->willReturn(count($resultItems));
$resultsProphet->get(0)->willReturn($resultItems[0]);
}
$result = $resultsProphet->reveal();
}
$mock = $this->createMock(XeroQuery::class);
$mock->expects($this->once())
->method('setType')
->with($type)
->willReturn($mock);
if ($method) {
$mock->expects($this->once())
->method('setMethod')
->with($method)
->willReturn($mock);
}
if ($item) {
$mock->expects($this->once())
->method('setData')
->with($this->callback(function ($data) use ($item) {
// We can't simply test for the equality of the objects,
// because a list's don't store the child objects internally,
// only their values. The child items are recreated when extracted.
$sameProperties = TRUE;
$itemProperties = array_keys($item->getSpecifiedProperties());
sort($itemProperties);
$dataProperties = array_keys($data->get(0)->getSpecifiedProperties());
sort($dataProperties);
$sameSpecified = $itemProperties === $dataProperties;
foreach ($itemProperties as $property) {
$sameProperty = $data->get(0)->get($property)->getString() === $item->get($property)->getString();
$sameProperties = $sameProperties && $sameProperty;
}
$single = $data->count() === 1;
return $single && $sameSpecified && $sameProperties;
}))
->willReturn($mock);
}
$mock->expects($this->once())
->method('execute')
->willReturn($result);
return $mock;
}
/**
* Construct a typed data representation of a Xero item.
*
* @param string $type
* The Xero type.
* @param array $properties
* The properties to initialise the item with.
*
* @return \Drupal\xero\TypedData\XeroComplexItemInterface
* The constructed item.
*/
protected function buildXeroItem($type, array $properties = []) {
$definition = \Drupal::typedDataManager()->createDataDefinition($type);
if (empty($properties)) {
$class = $definition->getClass();
$prop = $class::getXeroProperty('guid_name');
$properties[$prop] = $this->xeroId;
}
$item = \Drupal::typedDataManager()->create($definition, $properties);
$this->assertInstanceOf(XeroComplexItemInterface::class, $item);
return $item;
}
}
