From 255b26a56e20f3395458e5bfe5c610eca94200fd Mon Sep 17 00:00:00 2001 From: Adrien Barreau Date: Fri, 31 Mar 2023 15:26:30 +0000 Subject: [PATCH 1/2] feat: allow /v1 or /v2 prefixes in path - allow /v1 and /v2 - always send X-Ovh-Application - rewrite test mocks Signed-off-by: Adrien Barreau --- phpcs.xml | 5 +- src/Api.php | 23 +- tests/ApiTest.php | 615 +++++++++++++++++++--------------------------- 3 files changed, 281 insertions(+), 362 deletions(-) diff --git a/phpcs.xml b/phpcs.xml index b0e3834..cae0cb7 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -6,7 +6,10 @@ + + */tests/* + src tests - \ No newline at end of file + diff --git a/src/Api.php b/src/Api.php index c5ec8e6..43219a2 100644 --- a/src/Api.php +++ b/src/Api.php @@ -211,6 +211,24 @@ class Api return $response; } + /** + * getTarget returns the URL to target given an endpoint and a path. + * If the path starts with `/v1` or `/v2`, then remove the trailing `/1.0` from the endpoint. + * + * @param string path to use prefix from + * @return string + */ + protected function getTarget($path) : string + { + $endpoint = $this->endpoint; + if (substr($endpoint, -4) == '/1.0' && ( + substr($path, 0, 3) == '/v1' || + substr($path, 0, 3) == '/v2')) { + $endpoint = substr($endpoint, 0, strlen($endpoint)-4); + } + return $endpoint . $path; + } + /** * This is the main method of this wrapper. It will * sign a given query and return its result. @@ -238,7 +256,7 @@ class Api } } - $url = $this->endpoint . $path; + $url = $this->getTarget($path); $request = new Request($method, $url); if (isset($content) && $method === 'GET') { $query_string = $request->getUri()->getQuery(); @@ -280,9 +298,8 @@ class Api } $headers['Content-Type'] = 'application/json; charset=utf-8'; + $headers['X-Ovh-Application'] = $this->application_key ?? ''; if ($is_authenticated) { - $headers['X-Ovh-Application'] = $this->application_key; - if (!isset($this->time_delta)) { $this->calculateTimeDelta(); } diff --git a/tests/ApiTest.php b/tests/ApiTest.php index 20185ff..ec5c884 100644 --- a/tests/ApiTest.php +++ b/tests/ApiTest.php @@ -29,6 +29,8 @@ namespace Ovh\tests; use GuzzleHttp\Client; use GuzzleHttp\Exception\ClientException; +use GuzzleHttp\Handler\MockHandler; +use GuzzleHttp\HandlerStack; use GuzzleHttp\Middleware; use GuzzleHttp\Psr7; use GuzzleHttp\Psr7\Response; @@ -37,6 +39,29 @@ use Ovh\Api; use Ovh\Exceptions\InvalidParameterException; use PHPUnit\Framework\TestCase; +# Mock values +const MOCK_APPLICATION_KEY = "TDPKJdwZwAQPwKX2"; +const MOCK_APPLICATION_SECRET = "9ufkBmLaTQ9nz5yMUlg79taH0GNnzDjk"; +const MOCK_CONSUMER_KEY = "5mBuy6SUQcRw2ZUxg0cG68BoDKpED4KY"; +const MOCK_TIME = 1457018875; + +class MockClient extends Client +{ + public $calls = []; + + public function __construct(...$responses) + { + + $mock = new MockHandler($responses); + $history = Middleware::history($this->calls); + + $handlerStack = HandlerStack::create($mock); + $handlerStack->push($history); + + parent::__construct(['handler' => $handlerStack]); + } +} + /** * Test Api class * @@ -45,60 +70,6 @@ use PHPUnit\Framework\TestCase; */ class ApiTest extends TestCase { - /** - * @var Client - */ - private $client; - - /** - * @var string - */ - private $application_key; - - /** - * @var string - */ - private $consumer_key; - - /** - * @var string - */ - private $endpoint; - - /** - * @var string - */ - private $application_secret; - - /** - * Define id to create object - */ - protected function setUp() :void - { - $this->application_key = 'app_key'; - $this->application_secret = 'app_secret'; - $this->consumer_key = 'consumer'; - $this->endpoint = 'ovh-eu'; - - $this->client = new Client(); - } - - /** - * Get private and protected method to unit test it - * - * @param string $name - * - * @return \ReflectionMethod - */ - protected static function getPrivateMethod($name) - { - $class = new \ReflectionClass('Ovh\Api'); - $method = $class->getMethod($name); - $method->setAccessible(true); - - return $method; - } - /** * Get private and protected property to unit test it * @@ -121,7 +92,8 @@ class ApiTest extends TestCase public function testMissingApplicationKey() { $this->expectException(InvalidParameterException::class); - $api = new Api(null, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); + $this->expectExceptionMessage('Application key parameter is empty'); + $api = new Api(null, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, new MockClient()); $api->get('/me'); } @@ -131,7 +103,8 @@ class ApiTest extends TestCase public function testMissingApplicationSecret() { $this->expectException(InvalidParameterException::class); - $api = new Api($this->application_key, null, $this->endpoint, $this->consumer_key, $this->client); + $this->expectExceptionMessage('Application secret parameter is empty'); + $api = new Api(MOCK_APPLICATION_KEY, null, 'ovh-eu', MOCK_CONSUMER_KEY, new MockClient()); $api->get('/me'); } @@ -140,25 +113,18 @@ class ApiTest extends TestCase */ public function testNoCheckAppKeyForUnauthCall() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/unauthcall") { - return $request; - } + $client = new MockClient(new Response(200, [], '{}')); - return null; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('{}'); + $api = new Api(null, null, 'ovh-eu', null, $client); + $api->get("/me", null, null, false); - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - $api = new Api(null, null, $this->endpoint, $this->consumer_key, $this->client); - $api->get('/unauthcall', null, null, false); - $this->assertEquals(1, 1); + $calls = $client->calls; + $this->assertEquals(1, count($calls)); + + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/me', $req->getUri()->__toString()); + $this->assertEquals('', $req->getHeaderLine('X-Ovh-Application')); } /** @@ -167,7 +133,8 @@ class ApiTest extends TestCase public function testMissingApiEndpoint() { $this->expectException(InvalidParameterException::class); - new Api($this->application_key, $this->application_secret, null, $this->consumer_key, $this->client); + $this->expectExceptionMessage('Endpoint parameter is empty'); + new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, null, MOCK_CONSUMER_KEY, new MockClient()); } /** @@ -176,7 +143,8 @@ class ApiTest extends TestCase public function testBadApiEndpoint() { $this->expectException(InvalidParameterException::class); - new Api($this->application_key, $this->application_secret, 'i_am_invalid', $this->consumer_key, $this->client); + $this->expectExceptionMessage('Unknown provided endpoint'); + new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'i_am_invalid', MOCK_CONSUMER_KEY, new MockClient()); } /** @@ -184,8 +152,7 @@ class ApiTest extends TestCase */ public function testClientCreation() { - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key); - + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY); $this->assertInstanceOf('\\GuzzleHttp\\Client', $api->getHttpClient()); } @@ -194,28 +161,28 @@ class ApiTest extends TestCase */ public function testTimeDeltaCompute() { - $delay = 10; + $client = new MockClient( + new Response(200, [], time() - 10), + new Response(200, [], '{}'), + ); - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $api->get("/me"); - $body = Psr7\Utils::streamFor(time() - 10); - - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $invoker = self::getPrivateMethod('calculateTimeDelta'); $property = self::getPrivateProperty('time_delta'); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $invoker->invokeArgs($api, []); - $time_delta = $property->getValue($api); - $this->assertNotNull($time_delta); - $this->assertEquals($time_delta, $delay * -1); + $this->assertEquals($time_delta, -10); + + $calls = $client->calls; + $this->assertEquals(2, count($calls)); + + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString()); + + $req = $calls[1]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/me', $req->getUri()->__toString()); } /** @@ -223,32 +190,30 @@ class ApiTest extends TestCase */ public function testIfConsumerKeyIsReplace() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { + $client = new MockClient( + new Response(200, [], MOCK_TIME), + new Response( + 200, + ['Content-Type' => 'application/json; charset=utf-8'], + '{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}' + ), + ); - $body = Psr7\Utils::streamFor('{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}'); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $this->assertNotEquals('consumer_remote', $api->getConsumerKey()); + $credentials = $api->requestCredentials(['method' => 'GET', 'path' => '/*']); + $this->assertEquals('consumer_remote', $api->getConsumerKey()); - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); + $calls = $client->calls; + $this->assertEquals(2, count($calls)); - $property = self::getPrivateProperty('consumer_key'); + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString()); - $this->assertEquals('consumer', $this->consumer_key); - $this->assertNotEquals('consumer_remote', $this->consumer_key); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $accessRules = [json_decode(' { "method": "GET", "path": "/*" } ')]; - - $credentials = $api->requestCredentials($accessRules); - - $consumer_key = $property->getValue($api); - - $this->assertEquals($consumer_key, $credentials["consumerKey"]); - $this->assertEquals('consumer_remote', $credentials["consumerKey"]); - $this->assertNotEquals($consumer_key, $this->consumer_key); + $req = $calls[1]['request']; + $this->assertEquals('POST', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/credential', $req->getUri()->__toString()); } /** @@ -256,30 +221,15 @@ class ApiTest extends TestCase */ public function testInvalidApplicationKey() { - + $error = '{"class":"Client::Forbidden","message":"Invalid application key"}'; $this->expectException(ClientException::class); + $this->expectExceptionCode(403); + $this->expectExceptionMessage($error); - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { + $client = new MockClient(new Response(403, ['Content-Type' => 'application/json; charset=utf-8'], $error)); - $body = Psr7\Utils::streamFor('{\"message\":\"Invalid application key\"}'); - - return $response - ->withStatus(401, 'POUET') - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withHeader('Content-Length', 37) - ->withBody($body); - })); - - $property = self::getPrivateProperty('consumer_key'); - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $accessRules = [json_decode(' { "method": "GET", "path": "/*" } ')]; - - $credentials = $api->requestCredentials($accessRules); - $consumer_key = $property->getValue($api); - - $this->assertEquals($consumer_key, $credentials["consumerKey"]); - $this->assertNotEquals($consumer_key, $this->consumer_key); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $api->requestCredentials(['method' => 'GET', 'path' => '/*']); } /** @@ -287,30 +237,21 @@ class ApiTest extends TestCase */ public function testInvalidRight() { + $error = '{"message": "Invalid credentials"}'; $this->expectException(ClientException::class); + $this->expectExceptionCode(403); + $this->expectExceptionMessage($error); - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { + $client = new MockClient(new Response(403, ['Content-Type' => 'application/json; charset=utf-8'], $error)); - $body = Psr7\Utils::streamFor('{\"message\":\"Invalid credentials\"}'); - - return $response - ->withStatus(403) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withHeader('Content-Length', 37) - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - - $invoker = self::getPrivateMethod('rawCall'); - $invoker->invokeArgs($api, ['GET', '/me']); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $api->get('/me', null, null, false); } public function testGetConsumerKey() { - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $this->assertEquals($this->consumer_key, $api->getConsumerKey()); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY); + $this->assertEquals(MOCK_CONSUMER_KEY, $api->getConsumerKey()); } @@ -319,32 +260,17 @@ class ApiTest extends TestCase */ public function testGetQueryArgs() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } + $client = new MockClient(new Response(200, [], '{}')); - $query_string = $request->getUri()->getQuery(); - $this->assertEquals($query_string, 'applicationId=49&status=pendingValidation'); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $api->get('/me/api/credential?applicationId=49', ['status' => 'pendingValidation'], null, false); - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('123456789991'); + $calls = $client->calls; + $this->assertEquals(1, count($calls)); - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $api->get('/me/api/credential?applicationId=49', ['status' => 'pendingValidation']); + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/me/api/credential?applicationId=49&status=pendingValidation', $req->getUri()->__toString()); } /** @@ -352,32 +278,17 @@ class ApiTest extends TestCase */ public function testGetOverlappingQueryArgs() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } + $client = new MockClient(new Response(200, [], '{}')); - $query_string = $request->getUri()->getQuery(); - $this->assertEquals($query_string, 'applicationId=49&status=expired&test=success'); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $api->get('/me/api/credential?applicationId=49&status=pendingValidation', ['status' => 'expired', 'test' => "success"], null, false); - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('123456789991'); + $calls = $client->calls; + $this->assertEquals(1, count($calls)); - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $api->get('/me/api/credential?applicationId=49&status=pendingValidation', ['status' => 'expired', 'test' => "success"]); + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/me/api/credential?applicationId=49&status=expired&test=success', $req->getUri()->__toString()); } /** @@ -385,149 +296,41 @@ class ApiTest extends TestCase */ public function testGetBooleanQueryArgs() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } + $client = new MockClient(new Response(200, [], '{}')); - $query_string = $request->getUri()->getQuery(); - $this->assertEquals($query_string, 'dryRun=true¬DryRun=false'); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $api->get('/me/api/credential', ['dryRun' => true, 'notDryRun' => false], null, false); - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('123456789991'); + $calls = $client->calls; + $this->assertEquals(1, count($calls)); - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); - $api->get('/me/api/credential', ['dryRun' => true, 'notDryRun' => false]); + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/me/api/credential?dryRun=true¬DryRun=false', $req->getUri()->__toString()); } /** - * Test valid predefined endpoint + * Test valid provided endpoint */ - public function testPredefinedEndPoint() + public function testProvidedUrl() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } + foreach ([ + ['endpoint' => 'http://api.ovh.com/1.0', 'expectedUrl' => 'http://api.ovh.com/1.0'], + ['endpoint' => 'https://api.ovh.com/1.0', 'expectedUrl' => 'https://api.ovh.com/1.0'], + ['endpoint' => 'ovh-eu', 'expectedUrl' => 'https://eu.api.ovh.com/1.0'], + ] as $test) { + $client = new MockClient(new Response(200, [], '{}')); - $host = $request->getUri()->getHost(); - $this->assertEquals($host, 'ca.api.ovh.com'); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, $test['endpoint'], MOCK_CONSUMER_KEY, $client); + $api->get('/me/api/credential', null, null, false); - $resource = $request->getUri()->getPath(); - $this->assertEquals($resource, '/1.0/me/api/credential'); + $calls = $client->calls; + $this->assertEquals(1, count($calls)); - $resource = $request->getUri()->getScheme(); - $this->assertEquals($resource, 'https'); - - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('123456789991'); - - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, 'ovh-ca', $this->consumer_key, $this->client); - $api->get('/me/api/credential'); - } - - /** - * Test valid provided HTTP endpoint - */ - public function testProvidedHttpEndPoint() - { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } - - $host = $request->getUri()->getHost(); - $this->assertEquals($host, 'api.ovh.com'); - - $resource = $request->getUri()->getPath(); - $this->assertEquals($resource, '/1.0/me/api/credential'); - - $resource = $request->getUri()->getScheme(); - $this->assertEquals($resource, 'http'); - - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('123456789991'); - - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, 'http://api.ovh.com/1.0', $this->consumer_key, $this->client); - $api->get('/me/api/credential'); - } - - /** - * Test valid provided HTTPS endpoint - */ - public function testProvidedHttpsEndPoint() - { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } - - $host = $request->getUri()->getHost(); - $this->assertEquals($host, 'api.ovh.com'); - - $resource = $request->getUri()->getPath(); - $this->assertEquals($resource, '/1.0/me/api/credential'); - - $resource = $request->getUri()->getScheme(); - $this->assertEquals($resource, 'https'); - - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('123456789991'); - - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, 'https://api.ovh.com/1.0', $this->consumer_key, $this->client); - $api->get('/me/api/credential'); + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals($test['expectedUrl'] . '/me/api/credential', $req->getUri()->__toString()); + } } /** @@ -535,32 +338,128 @@ class ApiTest extends TestCase */ public function testMissingOvhApplicationHeaderOnRequestCredentials() { - $handlerStack = $this->client->getConfig('handler'); - $handlerStack->push(Middleware::mapRequest(function (Request $request) { - if ($request->getUri()->getPath() == "/1.0/auth/time") { - return $request; - } + $client = new MockClient( + new Response(200, [], MOCK_TIME), + new Response(200, [], '{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}'), + ); - $ovhApplication = $request->getHeader('X-OVH-Application'); - $this->assertNotNull($ovhApplication); - $this->assertEquals($ovhApplication, array($this->application_key)); - - $request = $request->withUri($request->getUri() - ->withHost('httpbin.org') - ->withPath('/') - ->withQuery('')); - return $request; - })); - $handlerStack->push(Middleware::mapResponse(function (Response $response) { - $body = Psr7\Utils::streamFor('{"validationUrl":"https://api.ovh.com/login/?credentialToken=token","consumerKey":"consumer_remote","state":"pendingValidation"}'); - - return $response - ->withStatus(200) - ->withHeader('Content-Type', 'application/json; charset=utf-8') - ->withBody($body); - })); - - $api = new Api($this->application_key, $this->application_secret, $this->endpoint, $this->consumer_key, $this->client); + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); $api->requestCredentials([]); + + $calls = $client->calls; + $this->assertEquals(2, count($calls)); + + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString()); + + $req = $calls[1]['request']; + $this->assertEquals('POST', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/credential', $req->getUri()->__toString()); + $this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application')); + $this->assertEquals(MOCK_CONSUMER_KEY, $req->getHeaderLine('X-Ovh-Consumer')); + $this->assertEquals(MOCK_TIME, $req->getHeaderLine('X-Ovh-Timestamp')); + } + + public function testCallSignature() + { + // GET /auth/time + $mocks = [new Response(200, [], MOCK_TIME)]; + // (GET,POST,PUT,DELETE) x (/auth,/unauth) + for ($i = 0; $i < 8; $i++) { + $mocks[] = new Response(200, [], '{}'); + } + $client = new MockClient(...$mocks); + + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + $body = ["a" => "b", "c" => "d"]; + + // Authenticated calls + $api->get('/auth'); + $api->post('/auth', $body); + $api->put('/auth', $body); + $api->delete('/auth'); + + // Unauth calls + $api->get('/unauth', null, null, false); + $api->post('/unauth', $body, null, false); + $api->put('/unauth', $body, null, false); + $api->delete('/unauth', null, null, false); + + $calls = $client->calls; + $this->assertEquals(9, count($calls)); + + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString()); + + foreach ([ + 1 => ['method' => 'GET', 'sig' => '$1$e9556054b6309771395efa467c22e627407461ad'], + 2 => ['method' => 'POST', 'sig' => '$1$ec2fb5c7a81f64723c77d2e5b609ae6f58a84fc1'], + 3 => ['method' => 'PUT', 'sig' => '$1$8a75a9e7c8e7296c9dbeda6a2a735eb6bd58ec4b'], + 4 => ['method' => 'DELETE', 'sig' => '$1$a1eecd00b3b02b6cf5708b84b9ff42059a950d85'], + ] as $i => $test) { + $req = $calls[$i]['request']; + $this->assertEquals($test['method'], $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth', $req->getUri()->__toString()); + $this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application')); + $this->assertEquals(MOCK_CONSUMER_KEY, $req->getHeaderLine('X-Ovh-Consumer')); + $this->assertEquals(MOCK_TIME, $req->getHeaderLine('X-Ovh-Timestamp')); + $this->assertEquals($test['sig'], $req->getHeaderLine('X-Ovh-Signature')); + if ($test['method'] == 'POST' || $test['method'] == 'PUT') { + $this->assertEquals('application/json; charset=utf-8', $req->getHeaderLine('Content-Type')); + } + } + + foreach (['GET', 'POST', 'PUT', 'DELETE'] as $i => $method) { + $req = $calls[$i + 5]['request']; + $this->assertEquals($method, $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/unauth', $req->getUri()->__toString()); + $this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application')); + $this->assertNotTrue($req->hasHeader('X-Ovh-Consumer')); + $this->assertNotTrue($req->hasHeader('X-Ovh-Timestamp')); + $this->assertNotTrue($req->hasHeader('X-Ovh-Signature')); + if ($method == 'POST' || $method == 'PUT') { + $this->assertEquals('application/json; charset=utf-8', $req->getHeaderLine('Content-Type')); + } + } + } + + public function testVersionInUrl() + { + // GET /auth/time + $mocks = [new Response(200, [], MOCK_TIME)]; + // GET) x (/1.0/call,/v1/call,/v2/call) + for ($i = 0; $i < 3; $i++) { + $mocks[] = new Response(200, [], '{}'); + } + $client = new MockClient(...$mocks); + + $api = new Api(MOCK_APPLICATION_KEY, MOCK_APPLICATION_SECRET, 'ovh-eu', MOCK_CONSUMER_KEY, $client); + + $api->get('/call'); + $api->get('/v1/call'); + $api->get('/v2/call'); + + $calls = $client->calls; + $this->assertEquals(4, count($calls)); + + $req = $calls[0]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/1.0/auth/time', $req->getUri()->__toString()); + + foreach ([ + 1 => ['path' => '1.0/call', 'sig' => '$1$7f2db49253edfc41891023fcd1a54cf61db05fbb'], + 2 => ['path' => 'v1/call', 'sig' => '$1$e6e7906d385eb28adcbfbe6b66c1528a42d741ad'], + 3 => ['path' => 'v2/call', 'sig' => '$1$bb63b132a6f84ad5433d0c534d48d3f7c3804285'], + ] as $i => $test) { + $req = $calls[$i]['request']; + $this->assertEquals('GET', $req->getMethod()); + $this->assertEquals('https://eu.api.ovh.com/' . $test['path'], $req->getUri()->__toString()); + $this->assertEquals(MOCK_APPLICATION_KEY, $req->getHeaderLine('X-Ovh-Application')); + $this->assertEquals(MOCK_CONSUMER_KEY, $req->getHeaderLine('X-Ovh-Consumer')); + $this->assertEquals(MOCK_TIME, $req->getHeaderLine('X-Ovh-Timestamp')); + $this->assertEquals($test['sig'], $req->getHeaderLine('X-Ovh-Signature')); + } } } From 73dab16efbc4c904dfef502c0da69c92af793513 Mon Sep 17 00:00:00 2001 From: Adrien Barreau Date: Fri, 31 Mar 2023 17:12:25 +0000 Subject: [PATCH 2/2] test: skip functional tests if no credentials are provided Signed-off-by: Adrien Barreau --- tests/ApiFunctionalTest.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/ApiFunctionalTest.php b/tests/ApiFunctionalTest.php index 97b692b..5c0de7c 100644 --- a/tests/ApiFunctionalTest.php +++ b/tests/ApiFunctionalTest.php @@ -86,6 +86,13 @@ class ApiFunctionalTest extends TestCase */ protected function setUp() :void { + foreach (['APP_KEY', 'APP_SECRET', 'CONSUMER', 'ENDPOINT'] as $envName) { + if (!getenv($envName)) { + $this->markTestSkipped("Skip test due to missing $envName variable"); + return; + } + } + $this->application_key = getenv('APP_KEY'); $this->application_secret = getenv('APP_SECRET'); $this->consumer_key = getenv('CONSUMER');