diff --git a/src/Auth/Abstracts/AbstractAuthController.php b/src/Auth/Abstracts/AbstractAuthController.php index 7d0ebdb..ea4550a 100644 --- a/src/Auth/Abstracts/AbstractAuthController.php +++ b/src/Auth/Abstracts/AbstractAuthController.php @@ -2,6 +2,7 @@ namespace MRussell\REST\Auth\Abstracts; +use Psr\Http\Message\ResponseInterface; use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Psr7\Response; use MRussell\REST\Auth\AuthControllerInterface; @@ -189,21 +190,24 @@ public function authenticate(): bool } } catch (RequestException $exception) { $response = $exception->getResponse(); - if ($response) { + if ($response instanceof ResponseInterface) { $statusCode = $response->getStatusCode(); $message = $exception->getMessage(); $content = $response->getBody()->getContents(); if (!empty($content)) { - $message .= "RESPONSE: $content"; + $message .= 'RESPONSE: ' . $content; } - $this->getLogger()->error("[REST] Authenticate Failed [$statusCode] - " . $message); + + $this->getLogger()->error(sprintf('[REST] Authenticate Failed [%d] - ', $statusCode) . $message); } else { $this->getLogger()->error("[REST] Authenticate Request Exception - " . $exception->getMessage()); } + // @codeCoverageIgnoreStart } catch (\Exception $exception) { $this->getLogger()->error("[REST] Authenticate Exception - " . $exception->getMessage()); } + // @codeCoverageIgnoreEnd return $ret; } @@ -226,21 +230,24 @@ public function logout(): bool $this->getLogger()->debug($exception->getMessage()); } catch (RequestException $exception) { $response = $exception->getResponse(); - if ($response) { + if ($response instanceof ResponseInterface) { $statusCode = $response->getStatusCode(); $message = $exception->getMessage(); $content = $response->getBody()->getContents(); if (!empty($content)) { - $message .= "RESPONSE: $content"; + $message .= 'RESPONSE: ' . $content; } - $this->getLogger()->error("[REST] Logout Failed [$statusCode] - " . $message); + + $this->getLogger()->error(sprintf('[REST] Logout Failed [%d] - ', $statusCode) . $message); } else { $this->getLogger()->error("[REST] Logout Request Exception - " . $exception->getMessage()); } + // @codeCoverageIgnoreStart } catch (\Exception $exception) { $this->getLogger()->error("[REST] Logout Exception - " . $exception->getMessage()); } + // @codeCoverageIgnoreEnd return $ret; } diff --git a/src/Auth/Abstracts/AbstractOAuth2Controller.php b/src/Auth/Abstracts/AbstractOAuth2Controller.php index 81d99db..df80a50 100644 --- a/src/Auth/Abstracts/AbstractOAuth2Controller.php +++ b/src/Auth/Abstracts/AbstractOAuth2Controller.php @@ -2,6 +2,7 @@ namespace MRussell\REST\Auth\Abstracts; +use Psr\Http\Message\ResponseInterface; use GuzzleHttp\Exception\RequestException; use Psr\SimpleCache\InvalidArgumentException; use GuzzleHttp\Psr7\Response; @@ -200,21 +201,24 @@ public function refresh(): bool $this->getLogger()->debug($exception->getMessage()); } catch (RequestException $exception) { $response = $exception->getResponse(); - if ($response) { + if ($response instanceof ResponseInterface) { $statusCode = $response->getStatusCode(); $message = $exception->getMessage(); $content = $response->getBody()->getContents(); if (!empty($content)) { - $message .= "RESPONSE: $content"; + $message .= 'RESPONSE: ' . $content; } - $this->getLogger()->error("[REST] OAuth Refresh Failed [$statusCode] - " . $message); + + $this->getLogger()->error(sprintf('[REST] OAuth Refresh Failed [%d] - ', $statusCode) . $message); } else { $this->getLogger()->error("[REST] OAuth Refresh Request Exception - " . $exception->getMessage()); } + // @codeCoverageIgnoreStart } catch (\Exception $exception) { $this->getLogger()->error("[REST] Unknown OAuth Refresh Exception - " . $exception->getMessage()); } + // @codeCoverageIgnoreEnd } diff --git a/src/Endpoint/Abstracts/AbstractEndpoint.php b/src/Endpoint/Abstracts/AbstractEndpoint.php index 2b6afc9..975cd2c 100644 --- a/src/Endpoint/Abstracts/AbstractEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractEndpoint.php @@ -56,6 +56,10 @@ abstract class AbstractEndpoint implements EndpointInterface, EventTriggerInterf public const AUTH_REQUIRED = 2; + public const URL_VAR_CHAR = '$'; + + public const URL_OPTIONAL_VAR_CHAR = ':'; + protected static array $_DEFAULT_PROPERTIES = [ self::PROPERTY_URL => '', self::PROPERTY_HTTP_METHOD => '', @@ -122,10 +126,44 @@ public function catchNon200Responses(bool $catch = true): static */ public function setUrlArgs(array $args): static { + if (!empty($args) && $this->needsUrlArgs()) { + $args = $this->normalizeUrlArgs($args); + } + $this->_urlArgs = $args; return $this; } + protected function normalizeUrlArgs(array $urlArgs): array + { + $vars = $this->extractUrlVariables(); + $argNum = 0; + $normalized = []; + foreach ($urlArgs as $key => $value) { + if (isset($vars[$key])) { + $normalized[$key] = $value; + if ($vars[$key]['index'] == $argNum) { + $argNum++; + } + } elseif (is_numeric($key) && !empty($value)) { + foreach ($vars as $var => $varProps) { + if (!isset($normalized[$var]) && !isset($urlArgs[$var])) { + if ($varProps['index'] == $key) { + $normalized[$var] = $value; + break; + } elseif ($varProps['index'] == $argNum) { + $normalized[$var] = $value; + break; + } + } + } + $argNum++; + } + } + + return $normalized; + } + /** * @inheritdoc */ @@ -208,7 +246,7 @@ public function getResponse(): Response|null public function getResponseBody(bool $associative = true): mixed { $response = $this->getResponse(); - return $response ? $this->getResponseContent($response, $associative) : null; + return $response instanceof Response ? $this->getResponseContent($response, $associative) : null; } public function getHttpClient(): Client @@ -267,6 +305,7 @@ function (RequestException $e) use ($options): void { if ($response instanceof Response) { $this->setResponse($response); } + if (isset($options['error']) && is_callable($options['error'])) { $options['error']($e); } @@ -320,6 +359,7 @@ public function buildRequest(): Request $data = $this->configurePayload(); $request = new Request($method, $url); $request = $this->configureJsonRequest($request); + $this->_request = $this->configureRequest($request, $data); return $this->_request; } @@ -381,53 +421,39 @@ protected function configureURL(array $urlArgs): string { $url = $this->getEndPointUrl(); $this->triggerEvent(self::EVENT_CONFIGURE_URL, $urlArgs); - if ($this->hasUrlArgs()) { - $urlArr = explode("/", $url); - $optional = false; - $optionNum = 0; - $keys = array_keys($urlArgs); - sort($keys); - foreach ($keys as $key) { - if (is_numeric($key)) { - $optionNum = $key; - break; - } - } - - foreach ($urlArr as $key => $urlPart) { - $replace = null; - if (str_contains($urlPart, static::$_URL_VAR_CHARACTER)) { - if (str_contains($urlPart, ':')) { - $optional = true; - $replace = ''; - } + if ($this->needsUrlArgs()) { + $url = $this->populateUrlWithArgs($url, $urlArgs); + } - $opt = str_replace([static::$_URL_VAR_CHARACTER, ':'], '', $urlPart); - if (isset($urlArgs[$opt])) { - $replace = $urlArgs[$opt]; - } + return $url; + } - if (isset($urlArgs[$optionNum]) && ($replace == '' || $replace == null)) { - $replace = $urlArgs[$optionNum]; - $optionNum += 1; - } + protected function populateUrlWithArgs(string $url, array $urlArgs): string + { + $urlArgs = $this->normalizeUrlArgs($urlArgs); + $variables = $this->extractUrlVariables($url); + $urlArr = explode("/", trim($url, "/")); + foreach ($variables as $variable => $props) { + $index = $props['index']; + $replace = $urlArgs[$index] ?? ""; + if (isset($urlArgs[$variable])) { + $replace = $urlArgs[$variable]; + } - if ($optional && $replace == '') { - $urlArr = array_slice($urlArr, 0, $key); + $pattern = preg_quote(static::URL_VAR_CHAR . static::URL_OPTIONAL_VAR_CHAR, "/") . "?" . preg_quote($variable, "/"); + if (empty($replace) && $props['optional']) { + foreach ($urlArr as $i => $urlPart) { + if (preg_match(sprintf('/%s/', $pattern), $urlPart)) { + $urlArr = array_slice($urlArr, 0, $i); break; } - - if ($replace !== null) { - $urlArr[$key] = $replace; - } } + } elseif (!empty($replace)) { + $urlArr = preg_replace(sprintf('/%s/', $pattern), $replace, $urlArr); } - - $url = implode("/", $urlArr); - $url = rtrim($url, "/"); } - return $url; + return rtrim(implode("/", $urlArr), "/"); } /** @@ -436,7 +462,7 @@ protected function configureURL(array $urlArgs): string */ private function verifyUrl(string $url): bool { - if (str_contains($url, static::$_URL_VAR_CHARACTER)) { + if (str_contains($url, static::URL_VAR_CHAR)) { throw new InvalidUrl([static::class, $url]); } @@ -446,7 +472,7 @@ private function verifyUrl(string $url): bool /** * Checks if Endpoint URL requires Arguments */ - protected function hasUrlArgs(): bool + protected function needsUrlArgs(): bool { $url = $this->getEndPointUrl(); $variables = $this->extractUrlVariables($url); @@ -455,15 +481,30 @@ protected function hasUrlArgs(): bool /** * Helper method for extracting variables via Regex from a passed in URL - * @param $url */ - protected function extractUrlVariables($url): array + protected function extractUrlVariables(string $url = null): array { + $url = $url ?? $this->getEndPointUrl(); $variables = []; - $pattern = "/(" . preg_quote(static::$_URL_VAR_CHARACTER) . ".*?[^\\/]*)/"; - if (preg_match_all($pattern, $url, $matches)) { - foreach ($matches as $match) { - $variables[] = $match[0]; + $varChar = preg_quote(static::URL_VAR_CHAR, "/"); + $urlArr = explode("/", trim($url, "/")); + $varIndex = 0; + foreach ($urlArr as $pathPart) { + $pattern = "/(" . $varChar . sprintf('[^%s]+)/', $varChar); + if (preg_match_all($pattern, $pathPart, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $optional = str_contains($match[0], static::URL_OPTIONAL_VAR_CHAR); + $var = str_replace([static::URL_VAR_CHAR, static::URL_OPTIONAL_VAR_CHAR], '', $match[0]); + if (!isset($variables[$var])) { + $variables[$var] = [ + 'index' => $varIndex, + 'optional' => $optional, + ]; + $varIndex++; + } else { + $variables[$var]['optional'] = $optional ? $variables[$var]['optional'] : $optional; + } + } } } diff --git a/src/Endpoint/Abstracts/AbstractModelEndpoint.php b/src/Endpoint/Abstracts/AbstractModelEndpoint.php index 36f87f5..8e71a3b 100644 --- a/src/Endpoint/Abstracts/AbstractModelEndpoint.php +++ b/src/Endpoint/Abstracts/AbstractModelEndpoint.php @@ -118,6 +118,26 @@ public function __call($name, $arguments): EndpointInterface throw new UnknownModelAction([static::class, $name]); } + public function setUrlArgs(array $args): static + { + parent::setUrlArgs($args); + $this->setIdFromUrlArgs(); + return $this; + } + + protected function setIdFromUrlArgs(): void + { + if (!empty($this->_urlArgs[static::MODEL_ID_VAR])) { + $id = $this->getId(); + if ($id != $this->_urlArgs[static::MODEL_ID_VAR] && !empty($id)) { + $this->clear(); + } + $prop = $this->getKeyProperty(); + $this->set($prop, $this->_urlArgs[static::MODEL_ID_VAR]); + unset($this->_urlArgs[static::MODEL_ID_VAR]); + } + } + /** * @inheritdoc */ @@ -290,14 +310,13 @@ protected function syncFromApi(array $model): void protected function configureURL(array $urlArgs): string { - if (empty($urlArgs[self::MODEL_ID_VAR])) { + if (empty($urlArgs[static::MODEL_ID_VAR])) { switch ($this->getCurrentAction()) { case self::MODEL_ACTION_CREATE: $urlArgs[self::MODEL_ID_VAR] = ''; break; default: - $idKey = $this->getKeyProperty(); - $id = $this->get($idKey); + $id = $this->getId(); $urlArgs[self::MODEL_ID_VAR] = (empty($id) ? '' : $id); } } diff --git a/src/Endpoint/Provider/AbstractEndpointProvider.php b/src/Endpoint/Provider/AbstractEndpointProvider.php index d49be93..4c204f6 100644 --- a/src/Endpoint/Provider/AbstractEndpointProvider.php +++ b/src/Endpoint/Provider/AbstractEndpointProvider.php @@ -52,6 +52,7 @@ protected function addEndpointRegistry(string $name, array $properties): void if (!isset($properties[self::ENDPOINT_CLASS])) { throw new InvalidRegistration([$name]); } + if (!isset($properties[self::ENDPOINT_NAME])) { $properties[self::ENDPOINT_NAME] = $name; } @@ -120,12 +121,14 @@ protected function isInVersionRange(string $version, array $ranges): bool if (is_numeric($compare)) { $compare = "=="; } + $internalComp = true; if (is_array($range)) { foreach ($range as $c => $v) { if (is_array($v)) { continue; } + if (!$this->isInVersionRange($version, [$c => $v])) { $internalComp = false; break; diff --git a/src/Endpoint/Provider/AbstractMultiVersionEndpointProvider.php b/src/Endpoint/Provider/AbstractMultiVersionEndpointProvider.php index 59f0fa0..ef6104e 100644 --- a/src/Endpoint/Provider/AbstractMultiVersionEndpointProvider.php +++ b/src/Endpoint/Provider/AbstractMultiVersionEndpointProvider.php @@ -9,8 +9,8 @@ protected function addEndpointRegistry(string $name, array $properties): void if (isset($properties[self::ENDPOINT_NAME])) { $properties[self::ENDPOINT_NAME] = $name; } - - $this->registry[] = $properties; + $next = count($this->registry); + parent::addEndpointRegistry("$next", $properties); } protected function getEndpointDefinition(string $name, string $version = null): array diff --git a/src/Endpoint/Traits/GenerateEndpointTrait.php b/src/Endpoint/Traits/GenerateEndpointTrait.php index c6aed1d..ac8e0a6 100644 --- a/src/Endpoint/Traits/GenerateEndpointTrait.php +++ b/src/Endpoint/Traits/GenerateEndpointTrait.php @@ -24,6 +24,7 @@ protected function generateEndpoint(string $endpoint): EndpointInterface|null $EP->setBaseUrl($this->getBaseUrl()); } } + return $EP; } } diff --git a/src/Endpoint/Traits/PropertiesTrait.php b/src/Endpoint/Traits/PropertiesTrait.php index 1df51bd..5b9ee98 100644 --- a/src/Endpoint/Traits/PropertiesTrait.php +++ b/src/Endpoint/Traits/PropertiesTrait.php @@ -2,8 +2,6 @@ namespace MRussell\REST\Endpoint\Traits; -use MRussell\REST\Endpoint\Interfaces\PropertiesInterface; - trait PropertiesTrait { protected array $_properties = []; diff --git a/tests/Endpoint/AbstractCollectionEndpointTest.php b/tests/Endpoint/AbstractCollectionEndpointTest.php index 1619c57..749e668 100644 --- a/tests/Endpoint/AbstractCollectionEndpointTest.php +++ b/tests/Endpoint/AbstractCollectionEndpointTest.php @@ -2,12 +2,14 @@ namespace MRussell\REST\Tests\Endpoint; +use MRussell\REST\Endpoint\Interfaces\ModelInterface; use MRussell\REST\Exception\Endpoint\UnknownEndpoint; use GuzzleHttp\Psr7\Response; use MRussell\REST\Endpoint\CollectionEndpoint; use MRussell\REST\Endpoint\ModelEndpoint; use MRussell\REST\Tests\Stubs\Client\Client; use MRussell\REST\Tests\Stubs\Endpoint\CollectionEndpointWithoutModel; +use MRussell\REST\Tests\Stubs\Endpoint\EndpointProvider; use MRussell\REST\Tests\Stubs\Endpoint\ModelEndpointWithActions; use PHPUnit\Framework\TestCase; @@ -28,6 +30,7 @@ class AbstractCollectionEndpointTest extends TestCase protected function setUp(): void { $this->client = new Client(); + $this->client->setEndpointProvider(new EndpointProvider()); parent::setUp(); } @@ -46,7 +49,7 @@ protected function tearDown(): void * @covers ::toArray * @covers ::get * @covers ::buildModel - * @covers MRussell\REST\Endpoint\Traits\GenerateEndpointTrait::generateEndpoint + * @covers \MRussell\REST\Endpoint\Traits\GenerateEndpointTrait::generateEndpoint * @covers ::clear * @covers ::reset * @covers ::at @@ -84,9 +87,10 @@ public function testDataAccess(): void $this->assertEquals(false, is_object($Model)); $Collection->setModelEndpoint(ModelEndpoint::class); $Model = $Collection->get('abc123'); - $this->assertEquals(true, is_object($Model)); + $this->assertInstanceOf(ModelInterface::class, $Model); $this->assertEquals('bar', $Model->get('foo')); $this->assertEquals($this->client, $Model->getClient()); + $Model = $Collection->at(1); $this->assertEquals(['id' => 'efg234', 'name' => 'test', 'foo' => ''], $Model->toArray()); $Model = $Collection->at(-1); @@ -117,6 +121,28 @@ public function testSetModelEndpoint(): void $this->assertEquals(ModelEndpoint::class, $Collection->getProperty('model')); $Collection->setModelEndpoint(\MRussell\REST\Tests\Stubs\Endpoint\ModelEndpoint::class); $this->assertEquals(\MRussell\REST\Tests\Stubs\Endpoint\ModelEndpoint::class, $Collection->getProperty('model')); + + $Collection->setClient($this->client); + $this->client->setEndpointProvider(new EndpointProvider()); + $this->client->getEndpointProvider()->registerEndpoint('testModel', ModelEndpoint::class); + $Collection->setModelEndpoint('testModel'); + $this->assertEquals(ModelEndpoint::class, $Collection->getProperty('model')); + } + + /** + * @covers \MRussell\REST\Endpoint\Traits\GenerateEndpointTrait::generateEndpoint + */ + public function testGenerateEndpoint(): void + { + $Collection = new CollectionEndpointWithoutModel(); + $Collection->setClient($this->client); + $this->client->getEndpointProvider()->registerEndpoint('model', ModelEndpoint::class); + $reflection = new \ReflectionClass($Collection); + $method = $reflection->getMethod('generateEndpoint'); + $method->setAccessible(true); + + $endpoint = $method->invoke($Collection, 'model'); + $this->assertInstanceOf(ModelEndpoint::class, $endpoint); } /** diff --git a/tests/Endpoint/AbstractEndpointProviderTest.php b/tests/Endpoint/AbstractEndpointProviderTest.php index 2caf658..4750517 100644 --- a/tests/Endpoint/AbstractEndpointProviderTest.php +++ b/tests/Endpoint/AbstractEndpointProviderTest.php @@ -70,8 +70,11 @@ public function testRegisterEndpoint(): EndpointProvider $Provider = new EndpointProvider(); $this->assertEquals($Provider, $Provider->registerEndpoint('auth', AuthEndpoint::class)); $this->assertEquals($Provider, $Provider->registerEndpoint('foo', Endpoint::class, ['url' => 'foobar', 'httpMethod' => "GET"])); - $this->assertEquals($Provider, $Provider->registerEndpoint('versioned', Endpoint::class, - ['url' => 'v2/test', 'httpMethod' => "GET", 'versions' => [ '>=' => '2.0']])); + $this->assertEquals($Provider, $Provider->registerEndpoint( + 'versioned', + Endpoint::class, + ['url' => 'v2/test', 'httpMethod' => "GET", 'versions' => [ '>=' => '2.0']], + )); $Class = new \ReflectionClass(EndpointProvider::class); $addEndpointRegistry = $Class->getMethod('addEndpointRegistry'); $addEndpointRegistry->setAccessible(true); @@ -80,14 +83,15 @@ public function testRegisterEndpoint(): EndpointProvider $property = $Class->getProperty('registry'); $property->setAccessible(true); + $register = $property->getValue($Provider); $this->assertNotEmpty($register); - $this->assertEquals( 'auth', $register['auth']['name']); + $this->assertEquals('auth', $register['auth']['name']); $this->assertTrue(isset($register['auth']['versions'])); $this->assertTrue(isset($register['auth']['properties'])); - $this->assertEquals( 'foo', $register['foo']['name']); + $this->assertEquals('foo', $register['foo']['name']); $this->assertTrue(isset($register['auth']['class'])); - $this->assertEquals( 'foobar', $register['foo']['properties']['url']); + $this->assertEquals('foobar', $register['foo']['properties']['url']); $this->assertFalse(isset($register['versioned']['properties']['versions'])); $this->assertEquals('bulk', $register['bulk']['name']); @@ -166,11 +170,11 @@ public function testIsInVersionRange(): void $isInVersionRange = $reflection->getMethod('isInVersionRange'); $isInVersionRange->setAccessible(true); - $this->assertTrue($isInVersionRange->invoke($Provider,'1.0',['1.0'])); - $this->assertTrue($isInVersionRange->invoke($Provider,'1.1',[ '>=' => '1.0' ])); - $this->assertTrue($isInVersionRange->invoke($Provider,'1.9.1',[ ['>=' => '1.0'], ["<" => "2.0"] ])); - $this->assertFalse($isInVersionRange->invoke($Provider,'2.1',[ ['>=' => '1.0'], ["<" => "2.0"] ])); - $this->assertFalse($isInVersionRange->invoke($Provider,'2.0.1',[ '>=' => '1.0', "<" => "2.0" ])); - $this->assertTrue($isInVersionRange->invoke($Provider,'1.9',[ ['>=' => ['1.0','1.1']], "<" => "2.0" ])); + $this->assertTrue($isInVersionRange->invoke($Provider, '1.0', ['1.0'])); + $this->assertTrue($isInVersionRange->invoke($Provider, '1.1', [ '>=' => '1.0' ])); + $this->assertTrue($isInVersionRange->invoke($Provider, '1.9.1', [ ['>=' => '1.0'], ["<" => "2.0"] ])); + $this->assertFalse($isInVersionRange->invoke($Provider, '2.1', [ ['>=' => '1.0'], ["<" => "2.0"] ])); + $this->assertFalse($isInVersionRange->invoke($Provider, '2.0.1', [ '>=' => '1.0', "<" => "2.0" ])); + $this->assertTrue($isInVersionRange->invoke($Provider, '1.9', [ ['>=' => ['1.0','1.1']], "<" => "2.0" ])); } } diff --git a/tests/Endpoint/AbstractEndpointTest.php b/tests/Endpoint/AbstractEndpointTest.php index e524580..447e2f8 100644 --- a/tests/Endpoint/AbstractEndpointTest.php +++ b/tests/Endpoint/AbstractEndpointTest.php @@ -17,11 +17,12 @@ use MRussell\REST\Tests\Stubs\Endpoint\DefaultedNonNullableData; use MRussell\REST\Tests\Stubs\Endpoint\PingEndpoint; use PHPUnit\Framework\TestCase; +use ReflectionClass; /** * Class AbstractEndpointTest * @package MRussell\REST\Tests\Endpoint - * @coversDefaultClass MRussell\REST\Endpoint\Abstracts\AbstractEndpoint + * @coversDefaultClass \MRussell\REST\Endpoint\Abstracts\AbstractEndpoint * @group AbstractEndpointTest */ class AbstractEndpointTest extends TestCase @@ -86,11 +87,26 @@ public function testConstructor(): void $this->assertEquals('$foo/$bar/$:test', $Endpoint->getEndPointUrl()); } + public function testCatchNon200Responses(): void + { + $Endpoint = new BasicEndpoint(); + $reflection = new ReflectionClass($Endpoint); + $catchNon200Responses = $reflection->getProperty('_catchNon200Responses'); + $catchNon200Responses->setAccessible(true); + $this->assertFalse($catchNon200Responses->getValue($Endpoint)); + $this->assertEquals($Endpoint, $Endpoint->catchNon200Responses()); + $this->assertTrue($catchNon200Responses->getValue($Endpoint)); + $this->assertEquals($Endpoint, $Endpoint->catchNon200Responses(false)); + $this->assertFalse($catchNon200Responses->getValue($Endpoint)); + $this->assertEquals($Endpoint, $Endpoint->catchNon200Responses(true)); + $this->assertTrue($catchNon200Responses->getValue($Endpoint)); + } + /** * @covers ::setUrlArgs * @covers ::getUrlArgs */ - public function testSetOptions(): void + public function testSetUrlArgs(): void { $Endpoint = new BasicEndpoint(); $this->assertEquals([], $Endpoint->getUrlArgs()); @@ -100,6 +116,66 @@ public function testSetOptions(): void $this->assertEquals([], $Endpoint->getUrlArgs()); } + /** + * @covers ::setUrlArgs + * @covers ::normalizeUrlArgs + */ + public function testNormalizeUrlArgs(): void + { + $Endpoint = new BasicEndpoint(); + $Endpoint->setProperties($this->properties); + $Endpoint->setUrlArgs($this->urlArgs); + + $normalized = $Endpoint->getUrlArgs(); + $this->assertNotEquals($this->urlArgs, $normalized); + $this->assertEquals([ + 'foo' => 'foo', + 'bar' => 'bar', + ], $normalized); + + //Verify that appending an arg, maps to last variable + $normalized[] = 'test'; + $urlArgs = $normalized; + $Endpoint->setUrlArgs($normalized); + $normalized = $Endpoint->getUrlArgs(); + $this->assertNotEquals($urlArgs, $normalized); + $this->assertEquals([ + 'foo' => 'foo', + 'bar' => 'bar', + 'test' => 'test', + ], $normalized); + + $urlArgs = [ + 'first', + 'test' => 'last', + 'middle', + ]; + $Endpoint->setUrlArgs($urlArgs); + $normalized = $Endpoint->getUrlArgs(); + $this->assertNotEquals($urlArgs, $normalized); + $this->assertEquals([ + 'foo' => 'first', + 'bar' => 'middle', + 'test' => 'last', + ], $normalized); + + //Test ignoring blank args + $urlArgs = [ + '', + 'first', + 'test' => 'last', + 'middle', + ]; + $Endpoint->setUrlArgs($urlArgs); + $normalized = $Endpoint->getUrlArgs(); + $this->assertNotEquals($urlArgs, $normalized); + $this->assertEquals([ + 'foo' => 'first', + 'bar' => 'middle', + 'test' => 'last', + ], $normalized); + } + /** * @covers ::setProperties * @covers ::getProperties @@ -215,14 +291,19 @@ public function testExecute(): void $this->assertEquals('http://localhost/basic', $request->getUri()->__toString()); $this->assertEquals('application/json', $request->getHeader('Content-Type')[0]); $this->assertEquals('GET', $request->getMethod()); + + $this->client->mockResponses->append(new Response(400)); + $Endpoint->catchNon200Responses(); + $this->assertEquals($Endpoint, $Endpoint->execute()); + $this->assertNotEmpty($Endpoint->getResponse()); + $this->assertEquals(400, $Endpoint->getResponse()->getStatusCode()); } - /** - * @expectedException MRussell\REST\Exception\Endpoint\InvalidUrl - */ public function testInvalidUrl(): void { + $this->client->mockResponses->append(new Response(200)); $Endpoint = new BasicEndpoint(); + $Endpoint->setClient($this->client); $this->assertEquals($Endpoint, $Endpoint->setBaseUrl('http://localhost')); $this->assertEquals($Endpoint, $Endpoint->setProperty('url', '$foo')); $this->assertEquals('$foo', $Endpoint->getEndPointUrl()); @@ -231,8 +312,40 @@ public function testInvalidUrl(): void $Endpoint->execute(); } + /** + * @covers ::needsUrlArgs + * @covers ::extractUrlVariables + */ + public function testUrlVariables(): void + { + $Endpoint = new BasicEndpoint(); + $Class = new \ReflectionClass(BasicEndpoint::class); + $needsUrlArgs = $Class->getMethod('needsUrlArgs'); + $extractUrlVariables = $Class->getMethod('extractUrlVariables'); + $needsUrlArgs->setAccessible(true); + $extractUrlVariables->setAccessible(true); + + $Endpoint->setProperty('url', 'test/$module/$:id/action/$:actionArg'); + $this->assertEquals(true, $needsUrlArgs->invoke($Endpoint)); + $variables = $extractUrlVariables->invoke($Endpoint); + $this->assertEquals([ + 'module', + 'id', + 'actionArg', + ], array_keys($variables)); + + $Endpoint->setProperty('url', 'test/$:module/$:id/$module'); + $this->assertEquals(true, $needsUrlArgs->invoke($Endpoint)); + $variables = $extractUrlVariables->invoke($Endpoint); + $this->assertEquals([ + 'module', + 'id', + ], array_keys($variables)); + } + /** * @covers ::configureUrl + * @covers ::populateUrlWithArgs * */ public function testConfigureUrl(): void @@ -253,7 +366,7 @@ public function testConfigureUrl(): void $this->assertEquals($Endpoint, $Endpoint->setProperty('url', '$foo/$bar/$:baz')); $this->assertEquals('bar/foo/1234', $method->invoke( $Endpoint, - ['foo' => 'bar', 0 => 'foo', 1 => 1234], + ['foo' => 'bar', 1 => 'foo', 2 => 1234], )); $this->assertEquals('bar/foo/1234', $method->invoke( $Endpoint, @@ -271,6 +384,12 @@ public function testConfigureUrl(): void $Endpoint, ['foo' => 'bar', 'bar' => 'foo', 'baz' => 'foz', 0 => 1234], )); + + $this->assertEquals($Endpoint, $Endpoint->setProperty('url', 'test/$:module/$:id/$module')); + $this->assertEquals('test/Accounts/1234/Accounts', $method->invoke( + $Endpoint, + ['Accounts','1234'], + )); } /** diff --git a/tests/Endpoint/AbstractModelEndpointTest.php b/tests/Endpoint/AbstractModelEndpointTest.php index 42dba50..bb7800e 100644 --- a/tests/Endpoint/AbstractModelEndpointTest.php +++ b/tests/Endpoint/AbstractModelEndpointTest.php @@ -36,6 +36,7 @@ protected function tearDown(): void /** * @covers ::defaultModelKey * @covers ::getKeyProperty + * @covers ::getId */ public function testModelIdKey(): void { @@ -43,13 +44,21 @@ public function testModelIdKey(): void $this->assertEquals('key', ModelEndpoint::defaultModelKey('key')); $this->assertEquals('key', ModelEndpoint::defaultModelKey()); $Model = new ModelEndpoint(); + $Model->set([ + 'key' => 'key_value', + 'id' => 'id_value', + ]); $this->assertEquals('key', $Model->getKeyProperty()); + $this->assertEquals('key_value', $Model->getId()); $this->assertEquals('id', ModelEndpoint::defaultModelKey('id')); $this->assertEquals('id', $Model->getKeyProperty()); + $this->assertEquals('id_value', $Model->getId()); $this->assertEquals('key', ModelEndpoint::defaultModelKey('key')); + $this->assertEquals('key_value', $Model->getId()); $this->assertEquals('key', $Model->getKeyProperty()); $this->assertEquals($Model, $Model->setProperty(ModelEndpoint::PROPERTY_MODEL_KEY, 'id')); $this->assertEquals('id', $Model->getKeyProperty()); + $this->assertEquals('id_value', $Model->getId()); ModelEndpoint::defaultModelKey('id'); } @@ -65,6 +74,25 @@ public function testConstructor(): void $this->assertEquals(['create' => "POST", 'retrieve' => "GET", 'update' => "PUT", 'delete' => "DELETE"], $actions->getValue($Model)); } + /** + * @covers ::setUrlArgs + * @covers ::setIdFromUrlArgs + */ + public function testSetUrlArgs(): void + { + $Model = new ModelEndpoint(); + $Model->setProperty('url', 'Accounts/$:id'); + $Model->setUrlArgs(['12345']); + $this->assertEquals([], $Model->getUrlArgs()); + $this->assertEquals('12345', $Model->getId()); + $Model['foo'] = 'bar'; + + $Model->setUrlArgs(['id' => '56789']); + $this->assertEquals([], $Model->getUrlArgs()); + $this->assertEquals('56789', $Model->getId()); + $this->assertEmpty($Model['foo']); + } + /** * @covers ::__call * @covers ::configureAction diff --git a/tests/Endpoint/AbstractSmartEndpointTest.php b/tests/Endpoint/AbstractSmartEndpointTest.php index 4faafe5..b8ac505 100644 --- a/tests/Endpoint/AbstractSmartEndpointTest.php +++ b/tests/Endpoint/AbstractSmartEndpointTest.php @@ -159,6 +159,12 @@ public function testSetData(): void ], $Endpoint->getData()->toArray()); $this->assertEquals('bar', $Endpoint->getData()->foo); + $Endpoint = new SmartEndpointNoData(); + $reflection = new \ReflectionClass($Endpoint); + $data = $reflection->getProperty('_data'); + $data->setAccessible(true); + $data->setValue($Endpoint, ['foo' => 'bar']); + $this->assertInstanceOf(DataInterface::class, $Endpoint->getData()); } /** diff --git a/tests/Endpoint/Data/ValidatedDataTest.php b/tests/Endpoint/Data/ValidatedDataTest.php index b68c21f..67b249e 100644 --- a/tests/Endpoint/Data/ValidatedDataTest.php +++ b/tests/Endpoint/Data/ValidatedDataTest.php @@ -6,7 +6,6 @@ use MRussell\REST\Exception\Endpoint\InvalidData; use MRussell\REST\Tests\Stubs\Endpoint\ValidatedData; use PHPUnit\Framework\TestCase; -use MRussell\REST\Endpoint\Traits\ArrayObjectAttributesTrait; /** * Class AbstractEndpointDataTest diff --git a/tests/Stubs/Endpoint/ValidatedData.php b/tests/Stubs/Endpoint/ValidatedData.php index de925ec..99a2d64 100644 --- a/tests/Stubs/Endpoint/ValidatedData.php +++ b/tests/Stubs/Endpoint/ValidatedData.php @@ -23,6 +23,7 @@ public function validate(): bool if (isset($this->invalid)) { return !$this->invalid; } + return parent::validate(); } }