From 0898618751d5f604aa6dcac0de9e44b0830dd1ab Mon Sep 17 00:00:00 2001 From: Aaron Bauman <aaron@messageagency.com> Date: Thu, 16 Feb 2017 23:17:32 -0500 Subject: [PATCH] Merged RestClient-tests into 8.x-3.x: test coverage for RestClient methods --- src/Rest/RestClient.php | 6 +- src/Rest/RestResponse_Describe.php | 5 +- tests/src/Unit/RestClientTest.php | 341 +++++++++++++++++++++++++---- 3 files changed, 304 insertions(+), 48 deletions(-) diff --git a/src/Rest/RestClient.php b/src/Rest/RestClient.php index 1a231a68..4215d400 100644 --- a/src/Rest/RestClient.php +++ b/src/Rest/RestClient.php @@ -587,11 +587,11 @@ class RestClient { $response = $this->apiCall("sobjects/{$name}/{$key}/{$value}", $params, 'PATCH', TRUE); // On update, upsert method returns an empty body. Retreive object id, so that we can return a consistent response. - if ($this->response->getStatusCode() == 204) { + if ($response->getStatusCode() == 204) { // We need a way to allow callers to distinguish updates and inserts. To // that end, cache the original response and reset it after fetching the // ID. - $this->original_response = $this->response; + $this->original_response = $response; $sf_object = $this->objectReadbyExternalId($name, $key, $value); return $sf_object->id(); } @@ -760,7 +760,7 @@ class RestClient { * If $name is given, an array of record types indexed by developer name. * Otherwise, an array of record type arrays, indexed by object type name. */ - public function getRecordTypes($name = NULL) { + public function getRecordTypes($name = NULL, $reset = FALSE) { $cache = $this->cache->get('salesforce:record_types'); // Force the recreation of the cache when it is older than CACHE_LIFETIME diff --git a/src/Rest/RestResponse_Describe.php b/src/Rest/RestResponse_Describe.php index b415e1b2..27754853 100644 --- a/src/Rest/RestResponse_Describe.php +++ b/src/Rest/RestResponse_Describe.php @@ -39,8 +39,11 @@ class RestResponse_Describe extends RestResponse { foreach ($response->data['fields'] as $field) { $this->fields[$field['name']] = $field; } - unset($response->data['fields']); + foreach ($response->data as $key => $value) { + if ($key == 'fields') { + continue; + } $this->$key = $value; } } diff --git a/tests/src/Unit/RestClientTest.php b/tests/src/Unit/RestClientTest.php index f65e3617..5c1c140d 100644 --- a/tests/src/Unit/RestClientTest.php +++ b/tests/src/Unit/RestClientTest.php @@ -5,9 +5,16 @@ namespace Drupal\Tests\salesforce\Unit; use Drupal\Tests\UnitTestCase; use Drupal\salesforce\Exception; use Drupal\salesforce\Rest\RestClient; -use Drupal\salesforce\Rest\RestResponse as RestResponse; +use Drupal\salesforce\Rest\RestResponse; +use Drupal\salesforce\Rest\RestResponse_Describe; +use Drupal\salesforce\Rest\RestResponse_Resources; use Drupal\salesforce\SFID; +use Drupal\salesforce\SObject; +use Drupal\salesforce\SelectQueryResult; +use Drupal\salesforce\SelectQuery; use GuzzleHttp\Psr7\Response as GuzzleResponse; +use GuzzleHttp\Psr7\Request as GuzzleRequest; +use GuzzleHttp\Exception\RequestException; /** * @coversDefaultClass \Drupal\salesforce\Rest\RestClient @@ -20,7 +27,8 @@ class RestClientTest extends UnitTestCase { public function setUp() { parent::setUp(); - $methods = [ + $this->salesforce_id = '1234567890abcde'; + $this->methods = [ 'getConsumerKey', 'getConsumerSecret', 'getRefreshToken', @@ -40,18 +48,34 @@ class RestClientTest extends UnitTestCase { ->disableOriginalConstructor() ->getMock(); $this->cache = $this->getMock('\Drupal\Core\Cache\CacheBackendInterface'); + } + + private function initClient($methods = NULL) { + if (empty($methods)) { + $methods = $this->methods; + } + $args = [$this->httpClient, $this->configFactory, $this->state, $this->cache]; $this->client = $this->getMock(RestClient::CLASS, $methods, $args); - $this->client->expects($this->any()) - ->method('getApiEndPoint') - ->willReturn('https://example.com'); + + if (in_array('getApiEndPoint', $methods)) { + $this->client->expects($this->any()) + ->method('getApiEndPoint') + ->willReturn('https://example.com'); + } + if (in_array('getAccessToken', $methods)) { + $this->client->expects($this->any()) + ->method('getAccessToken') + ->willReturn(TRUE); + } } /** * @covers ::isAuthorized */ public function testAuthorized() { + $this->initClient(); $this->client->expects($this->at(0)) ->method('getConsumerKey') ->willReturn($this->randomMachineName()); @@ -72,14 +96,12 @@ class RestClientTest extends UnitTestCase { * @covers ::apiCall */ public function testSimpleApiCall() { + $this->initClient(); + // Test that an apiCall returns a json-decoded value. $body = array('foo' => 'bar'); $response = new GuzzleResponse(200, [], json_encode($body)); - $this->client->expects($this->any()) - ->method('getAccessToken') - ->willReturn(TRUE); - $this->client->expects($this->any()) ->method('httpRequest') ->willReturn($response); @@ -93,12 +115,11 @@ class RestClientTest extends UnitTestCase { * @expectedException Exception */ public function testExceptionApiCall() { + $this->initClient(); + // Test that SF client throws an exception for non-200 response $response = new GuzzleResponse(456); - $this->client->expects($this->any()) - ->method('getAccessToken') - ->willReturn(TRUE); $this->client->expects($this->any()) ->method('httpRequest') ->willReturn($response); @@ -110,13 +131,12 @@ class RestClientTest extends UnitTestCase { * @covers ::apiCall */ public function testReauthApiCall() { + $this->initClient(); + // Test that apiCall does auto-re-auth after 401 response $response_401 = new GuzzleResponse(401); $response_200 = new GuzzleResponse(200); - $this->client->expects($this->any()) - ->method('getAccessToken') - ->willReturn(TRUE); // First httpRequest() is position 4. // @TODO this is extremely brittle, exposes complexity in underlying client. Refactor this. $this->client->expects($this->at(3)) @@ -134,27 +154,142 @@ class RestClientTest extends UnitTestCase { * @covers ::objects */ public function testObjects() { - + $this->initClient(array_merge($this->methods, ['apiCall'])); + $objects = [ + 'sobjects' => [ + 'Test' => [ + 'updateable' => TRUE, + ], + 'NonUpdateable' => [ + 'updateable' => FALSE, + ] + ], + ]; + $cache = (object)[ + 'created' => time(), + 'data' => $objects, + ]; + unset($cache->data['sobjects']['NonUpdateable']); + + $this->cache->expects($this->at(0)) + ->method('get') + ->willReturn($cache); + $this->cache->expects($this->at(1)) + ->method('get') + ->willReturn(FALSE); + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($objects); + + // First call, from cache: + $this->assertEquals($cache->data['sobjects'], $this->client->objects()); + + // Second call, from apiCall() + $this->assertEquals($cache->data['sobjects'], $this->client->objects()); } /** * @covers ::query */ public function testQuery() { + $this->initClient(array_merge($this->methods, ['apiCall'])); + $rawQueryResult = [ + 'totalSize' => 1, + 'done' => true, + 'records' => [ + 0 => [ + 'attributes' => [ + 'type' => 'Foo', + 'url' => 'Bar' + ], + 'Id' => $this->salesforce_id, + ], + ], + ]; + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($rawQueryResult); + + // @TODO this doesn't seem like a very good test. + $this->assertEquals(new SelectQueryResult($rawQueryResult), $this->client->query(new SelectQuery(""))); } /** * @covers ::objectDescribe + * + * @expectedException Exception */ public function testObjectDescribe() { - + $this->initClient(array_merge($this->methods, ['apiCall'])); + $name = $this->randomMachineName(); + // @TODO this is fugly, do we need a refactor on RestResponse? + $restResponse = new RestResponse( + new GuzzleResponse('200', [], json_encode([ + 'name' => $name, + 'fields' => [ + [ + 'name' => $this->randomMachineName(), + 'label' => 'Foo Bar', + $this->randomMachineName() => $this->randomMachineName(), + $this->randomMachineName() => [ + $this->randomMachineName() => $this->randomMachineName(), + $this->randomMachineName() => $this->randomMachineName() + ], + ], + [ + 'name' => $this->randomMachineName(), + ], + ], + ])) + ); + + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($restResponse); + + // Test that we hit "apiCall" and get expected result: + $result = $this->client->objectDescribe($name); + $expected = new RestResponse_Describe($restResponse); + $this->assertEquals($expected, $result); + + // Test that cache gets set correctly: + $this->cache->expects($this->any()) + ->method('get') + ->willReturn((object)[ + 'data' => $expected, + 'created' => time() + ]); + + // Test that we hit cache when we call again. + // (Otherwise, we'll blow the "once" condition) + $this->assertEquals($expected, $this->client->objectDescribe($name)); + + // @TODO what happens when we provide a name for non-existent SF table? + // 404 exception? + + // Test that we throw an exception if name is not provided. + $this->client->objectDescribe(''); } /** * @covers ::objectCreate */ public function testObjectCreate() { + $this->initClient(array_merge($this->methods, ['apiCall'])); + $restResponse = new RestResponse( + new GuzzleResponse('200', [], json_encode([ + 'id' => $this->salesforce_id + ])) + ); + + $sfid = new SFID($this->salesforce_id); + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($restResponse); + + // @TODO this doesn't seem like a very good test. + $this->assertEquals($sfid, $this->client->objectCreate('', [])); } @@ -162,21 +297,68 @@ class RestClientTest extends UnitTestCase { * @covers ::objectUpsert */ public function testObjectUpsert() { + $this->initClient(array_merge($this->methods, [ + 'apiCall', + 'objectReadbyExternalId', + ])); + $createResponse = new RestResponse( + new GuzzleResponse('200', [], json_encode([ + 'id' => $this->salesforce_id + ])) + ); + + $updateResponse = new RestResponse(new GuzzleResponse('204', [], '')); + + $sfid = new SFID($this->salesforce_id); + $sobject = new SObject([ + 'id' => $this->salesforce_id, + 'attributes' => ['type' => 'dummy'], + ]); + $this->client->expects($this->at(0)) + ->method('apiCall') + ->willReturn($createResponse); + + $this->client->expects($this->at(1)) + ->method('apiCall') + ->willReturn($updateResponse); + + $this->client->expects($this->once()) + ->method('objectReadbyExternalId') + ->willReturn($sobject); + // Ensure both upsert-create and upsert-update return the same value. + $this->assertEquals($sfid, $this->client->objectUpsert('', '', '', [])); + $this->assertEquals($sfid, $this->client->objectUpsert('', '', '', [])); } /** * @covers ::objectUpdate */ public function testObjectUpdate() { - + $this->initClient(array_merge($this->methods, [ + 'apiCall', + ])); + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn(NULL); + $this->assertNull($this->client->objectUpdate('', '', [])); } /** * @covers ::objectRead */ public function testObjectRead() { - + $this->initClient(array_merge($this->methods, [ + 'apiCall', + ])); + $rawData = [ + 'id' => $this->salesforce_id, + 'attributes' => ['type' => 'dummy'], + ]; + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($rawData); + $this->assertEquals(new SObject($rawData), $this->client->objectRead('', '')); } /** @@ -186,56 +368,127 @@ class RestClientTest extends UnitTestCase { * @author Aaron Bauman */ public function testObjectReadbyExternalId() { - + $this->initClient(array_merge($this->methods, [ + 'apiCall', + ])); + $rawData = [ + 'id' => $this->salesforce_id, + 'attributes' => ['type' => 'dummy'], + ]; + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($rawData); + $this->assertEquals(new SObject($rawData), $this->client->objectReadByExternalId('', '', '')); } /** * @covers ::objectDelete + * + * @expectedException GuzzleHttp\Exception\RequestException */ public function testObjectDelete() { + $this->initClient(array_merge($this->methods, [ + 'apiCall', + ])); - } + // 3 tests for objectDelete: + // 1. test that a successful delete returns null + // 2. test that a 404 response gets eaten + // 3. test that any other error response percolates + $this->client->expects($this->exactly(3)) + ->method('apiCall'); - /** - * @covers ::getDeleted - */ - public function getDeleted() { + $this->client->expects($this->at(0)) + ->method('apiCall') + ->willReturn(NULL); - } + $exception404 = new RequestException('', new GuzzleRequest('', ''), new GuzzleResponse(404, [], '')); + $this->client->expects($this->at(1)) + ->method('apiCall') + ->will($this->throwException($exception404)); - /** - * @covers ::listResources - */ - public function testListResources() { + // Test the objectDelete throws any other exception. + $exceptionOther = new RequestException('', new GuzzleRequest('', ''), new GuzzleResponse(456, [], '')); + $this->client->expects($this->at(2)) + ->method('apiCall') + ->will($this->throwException($exceptionOther)); + $this->assertNull($this->client->objectDelete('', '')); + $this->assertNull($this->client->objectDelete('', '')); + $this->client->objectDelete('', ''); } /** - * @covers ::getUpdated + * @covers ::listResources */ - public function testGetUpdated() { - + public function testListResources() { + $this->initClient(array_merge($this->methods, [ + 'apiCall', + ])); + $restResponse = new RestResponse(new GuzzleResponse('204', [], json_encode([ + 'foo' => 'bar', + 'zee' => 'bang', + ]))); + $this->client->expects($this->once()) + ->method('apiCall') + ->willReturn($restResponse); + $this->assertEquals(new RestResponse_Resources($restResponse), $this->client->listResources()); } /** * @covers ::getRecordTypes + * + * @expectedException Exception */ public function testGetRecordTypes() { + $this->initClient(array_merge($this->methods, ['query'])); + $SobjectType = $this->randomMachineName(); + $DeveloperName = $this->randomMachineName(); + + $rawQueryResult = [ + 'totalSize' => 1, + 'done' => true, + 'records' => [ + 0 => [ + 'attributes' => [ + 'type' => 'Foo', + 'url' => 'Bar' + ], + 'SobjectType' => $SobjectType, + 'DeveloperName' => $DeveloperName, + 'Id' => $this->salesforce_id, + ], + ], + ]; + $recordTypes = [ + $SobjectType => [ + $DeveloperName => + new SObject($rawQueryResult['records'][0]) + ], + ]; + $cache = (object)[ + 'created' => time(), + 'data' => $recordTypes, + ]; - } - - /** - * @covers ::getRecordTypeIdByDeveloperName - */ - public function testGetRecordTypeIdByDeveloperName() { + $this->cache->expects($this->at(1)) + ->method('get') + ->willReturn(FALSE); + $this->cache->expects($this->at(2)) + ->method('get') + ->willReturn($cache); + $this->cache->expects($this->at(3)) + ->method('get') + ->willReturn($cache); + $this->client->expects($this->once()) + ->method('query') + ->willReturn(new SelectQueryResult($rawQueryResult)); - } + $this->assertEquals($recordTypes, $this->client->getRecordTypes()); - /** - * @covers ::getObjectTypeName - */ - public static function testGetObjectTypeName() { + $this->assertEquals($recordTypes[$SobjectType], $this->client->getRecordTypes($SobjectType)); + $this->client->getRecordTypes('fail'); } } -- GitLab