Skip to content

Commit

Permalink
Merge e4d3482 into 08698de
Browse files Browse the repository at this point in the history
  • Loading branch information
OndraM committed Nov 14, 2017
2 parents 08698de + e4d3482 commit a5b4dc9
Show file tree
Hide file tree
Showing 13 changed files with 275 additions and 27 deletions.
126 changes: 126 additions & 0 deletions src/Http/RequestManager.php
@@ -0,0 +1,126 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Http;

use Http\Client\Common\Plugin\AuthenticationPlugin;
use Http\Client\Common\PluginClient;
use Http\Client\HttpClient;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
use Http\Message\MessageFactory;
use Lmc\Matej\Http\Plugin\ExceptionPlugin;
use Lmc\Matej\Model\Request;
use Lmc\Matej\Model\Response;
use Psr\Http\Message\RequestInterface;

/**
* Encapsulates HTTP layer, ie. request/response handling.
* This class should typically not be used directly - its supposed to be called internally from `Matej` class.
*/
class RequestManager
{
/** @var string */
protected $accountId;
/** @var string */
protected $apiKey;
/** @var HttpClient */
protected $httpClient;
/** @var MessageFactory */
protected $messageFactory;
/** @var ResponseDecoderInterface */
protected $responseDecoder;

public function __construct(string $accountId, string $apiKey)
{
$this->accountId = $accountId;
$this->apiKey = $apiKey;
}

public function sendRequest(Request $request): Response
{
$httpRequest = $this->createHttpRequestFromMatejRequest($request);

$client = $this->createConfiguredHttpClient();

$httpResponse = $client->sendRequest($httpRequest);

return $this->getResponseDecoder()->decode($httpResponse);
}

/** @codeCoverageIgnore */
public function setHttpClient(HttpClient $httpClient): void
{
$this->httpClient = $httpClient;
}

/** @codeCoverageIgnore */
public function setMessageFactory(MessageFactory $messageFactory): void
{
$this->messageFactory = $messageFactory;
}

/** @codeCoverageIgnore */
public function setResponseDecoder(ResponseDecoderInterface $responseDecoder): void
{
$this->responseDecoder = $responseDecoder;
}

protected function getHttpClient(): HttpClient
{
if ($this->httpClient === null) {
// @codeCoverageIgnoreStart
$this->httpClient = HttpClientDiscovery::find();
// @codeCoverageIgnoreEnd
}

return $this->httpClient;
}

protected function getMessageFactory(): MessageFactory
{
if ($this->messageFactory === null) {
$this->messageFactory = MessageFactoryDiscovery::find();
}

return $this->messageFactory;
}

protected function getResponseDecoder(): ResponseDecoderInterface
{
if ($this->responseDecoder === null) {
$this->responseDecoder = new ResponseDecoder();
}

return $this->responseDecoder;
}

protected function createConfiguredHttpClient(): HttpClient
{
return new PluginClient(
$this->getHttpClient(),
[
new AuthenticationPlugin(new HmacAuthentication($this->apiKey)),
new ExceptionPlugin(),
]
);
}

protected function createHttpRequestFromMatejRequest(Request $request): RequestInterface
{
$requestBody = json_encode($request->getData());
$uri = $this->buildBaseUrl() . $request->getPath();

return $this->getMessageFactory()
->createRequest(
$request->getMethod(),
$uri,
['Content-Type' => 'application/json'],
$requestBody
);
}

protected function buildBaseUrl(): string
{
return sprintf('https://%s.matej.lmc.cz', $this->accountId);
}
}
3 changes: 1 addition & 2 deletions src/Http/ResponseDecoderInterface.php
@@ -1,5 +1,4 @@
<?php
declare(strict_types=1);
<?php declare(strict_types=1);

namespace Lmc\Matej\Http;

Expand Down
38 changes: 38 additions & 0 deletions src/Model/Request.php
@@ -0,0 +1,38 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Model;

/**
* Represents request to Matej prepared to be executed by `RequestManager`.
*/
class Request
{
/** @var string */
private $path;
/** @var string */
private $method;
/** @var array */
private $data;

public function __construct(string $path, string $method, array $data)
{
$this->path = $path;
$this->method = $method;
$this->data = $data;
}

public function getPath(): string
{
return $this->path;
}

public function getMethod(): string
{
return $this->method;
}

public function getData(): array
{
return $this->data;
}
}
5 changes: 2 additions & 3 deletions tests/Exception/AuthorizationExceptionTest.php
@@ -1,12 +1,11 @@
<?php
declare(strict_types=1);
<?php declare(strict_types=1);

namespace Lmc\Matej\Exception;

use Fig\Http\Message\StatusCodeInterface;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Lmc\Matej\TestCase;

class AuthorizationExceptionTest extends TestCase
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Exception/RequestExceptionTest.php
Expand Up @@ -5,7 +5,7 @@
use Fig\Http\Message\StatusCodeInterface;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use Lmc\Matej\TestCase;

class RequestExceptionTest extends TestCase
{
Expand Down
2 changes: 1 addition & 1 deletion tests/Http/HmacAuthenticationTest.php
Expand Up @@ -2,8 +2,8 @@

namespace Lmc\Matej\Http;

use Lmc\Matej\TestCase;
use phpmock\phpunit\PHPMock;
use PHPUnit\Framework\TestCase;

class HmacAuthenticationTest extends TestCase
{
Expand Down
3 changes: 1 addition & 2 deletions tests/Http/Plugin/ExceptionPluginTest.php
@@ -1,5 +1,4 @@
<?php
declare(strict_types=1);
<?php declare(strict_types=1);

namespace Lmc\Matej\Http\Plugin;

Expand Down
57 changes: 57 additions & 0 deletions tests/Http/RequestManagerTest.php
@@ -0,0 +1,57 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Http;

use Fig\Http\Message\RequestMethodInterface;
use Http\Mock\Client;
use Lmc\Matej\Model\Request;
use Lmc\Matej\Model\Response;
use Lmc\Matej\TestCase;

/**
* @covers \Lmc\Matej\Http\RequestManager
*/
class RequestManagerTest extends TestCase
{
/**
* Test sending request and decoding response - but isolated from the real HTTP using Http\Mock\Client.
* However all other RequestManager dependencies are real, making this partially integration test.
*
* @test
*/
public function shouldSendAndDecodeRequest(): void
{
$dummyHttpResponse = $this->createJsonResponseFromFile(__DIR__ . '/Fixtures/response-one-successful-command.json');

$mockClient = new Client();
$mockClient->addResponse($dummyHttpResponse);

$requestManager = new RequestManager('account-id', 'api-key');
$requestManager->setHttpClient($mockClient);

$request = new Request(
'/foo/endpoint',
RequestMethodInterface::METHOD_PUT,
['foo' => 'bar', 'list' => ['lorem' => 'ipsum', 'dolor' => 333]]
);

$matejResponse = $requestManager->sendRequest($request);

// Request should be decoded to Matej Response; decoding itself is comprehensively tested in ResponseDecoderTest
$this->assertInstanceOf(Response::class, $matejResponse);

// Assert properties of the send request
$recordedRequests = $mockClient->getRequests();
$this->assertCount(1, $recordedRequests);
$this->assertRegExp(
'~https\://account\-id\.matej\.lmc\.cz/foo/endpoint\?hmac_timestamp\=[0-9]+&hmac_sign\=[[:alnum:]]~',
$recordedRequests[0]->getUri()->__toString()
);
$this->assertSame(RequestMethodInterface::METHOD_PUT, $recordedRequests[0]->getMethod());
$this->assertJsonStringEqualsJsonString(
'{"foo":"bar","list":{"lorem":"ipsum","dolor":333}}',
$recordedRequests[0]->getBody()->__toString()
);
$this->assertSame(['application/json'], $recordedRequests[0]->getHeader('Content-Type'));
}
}
14 changes: 2 additions & 12 deletions tests/Http/ResponseDecoderTest.php
@@ -1,14 +1,12 @@
<?php
declare(strict_types=1);
<?php declare(strict_types=1);

namespace Lmc\Matej\Http;

use Fig\Http\Message\StatusCodeInterface;
use GuzzleHttp\Psr7\Response;
use Lmc\Matej\Exception\ResponseDecodingException;
use Lmc\Matej\Model\CommandResponse;
use PHPUnit\Framework\TestCase;
use Psr\Http\Message\ResponseInterface;
use Lmc\Matej\TestCase;

class ResponseDecoderTest extends TestCase
{
Expand Down Expand Up @@ -69,12 +67,4 @@ public function shouldThrowExceptionWhenDecodingFails(): void
$this->expectExceptionMessage('<p>The requested URL /foo was not found on this server.</p>');
$this->decoder->decode($response);
}

private function createJsonResponseFromFile(string $fileName): ResponseInterface
{
$jsonData = file_get_contents($fileName);
$response = new Response(StatusCodeInterface::STATUS_OK, ['Content-Type' => 'application/json'], $jsonData);

return $response;
}
}
5 changes: 2 additions & 3 deletions tests/Model/CommandResponseTest.php
@@ -1,10 +1,9 @@
<?php
declare(strict_types=1);
<?php declare(strict_types=1);

namespace Lmc\Matej\Model;

use Lmc\Matej\Exception\InvalidDomainModelArgumentException;
use PHPUnit\Framework\TestCase;
use Lmc\Matej\TestCase;

class CommandResponseTest extends TestCase
{
Expand Down
24 changes: 24 additions & 0 deletions tests/Model/RequestTest.php
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

namespace Lmc\Matej\Model;

use Fig\Http\Message\RequestMethodInterface;
use Lmc\Matej\TestCase;

class RequestTest extends TestCase
{
/** @test */
public function shouldBeInstantiable(): void
{
$path = '/foo/endpoint';
$method = RequestMethodInterface::METHOD_GET;
$data = ['foo' => 'bar', ['lorem' => 'ipsum']];

$request = new Request($path, $method, $data);

$this->assertInstanceOf(Request::class, $request);
$this->assertSame($path, $request->getPath());
$this->assertSame($method, $request->getMethod());
$this->assertSame($data, $request->getData());
}
}
5 changes: 2 additions & 3 deletions tests/Model/ResponseTest.php
@@ -1,10 +1,9 @@
<?php
declare(strict_types=1);
<?php declare(strict_types=1);

namespace Lmc\Matej\Model;

use Lmc\Matej\Exception\InvalidDomainModelArgumentException;
use PHPUnit\Framework\TestCase;
use Lmc\Matej\TestCase;

class ResponseTest extends TestCase
{
Expand Down
18 changes: 18 additions & 0 deletions tests/TestCase.php
@@ -0,0 +1,18 @@
<?php declare(strict_types=1);

namespace Lmc\Matej;

use Fig\Http\Message\StatusCodeInterface;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;

class TestCase extends \PHPUnit\Framework\TestCase
{
protected function createJsonResponseFromFile(string $fileName): ResponseInterface
{
$jsonData = file_get_contents($fileName);
$response = new Response(StatusCodeInterface::STATUS_OK, ['Content-Type' => 'application/json'], $jsonData);

return $response;
}
}

0 comments on commit a5b4dc9

Please sign in to comment.