diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..683d884 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.idea +.phpunit.result.cache +build +composer.lock +vendor \ No newline at end of file diff --git a/Tests/ApiTestCase.php b/Tests/ApiTestCase.php index 2657f16..7b925cf 100644 --- a/Tests/ApiTestCase.php +++ b/Tests/ApiTestCase.php @@ -18,7 +18,7 @@ abstract class ApiTestCase extends TestCase * @return mixed * @throws ReflectionException */ - public function callMethod(object $object, string $methodName, array $arguments = array()): mixed + public function callMethod(object $object, string $methodName, array $arguments = array()) { $class = new ReflectionClass($object); $method = $class->getMethod($methodName); @@ -50,7 +50,7 @@ abstract class ApiTestCase extends TestCase * @return mixed * @throws ReflectionException */ - public function getValue(object $object, string $valueName): mixed + public function getValue(object $object, string $valueName) { $reflection = new ReflectionClass($object); $property = $reflection->getProperty($valueName); diff --git a/Tests/ClientTest.php b/Tests/ClientTest.php index c92ceda..a1b7be6 100644 --- a/Tests/ClientTest.php +++ b/Tests/ClientTest.php @@ -1,28 +1,286 @@ + * @link http://www.oxidmodule.com + */ + +declare( strict_types = 1 ); + namespace D3\LinkmobilityClient\Tests; +use Assert\InvalidArgumentException; use D3\LinkmobilityClient\Client; +use D3\LinkmobilityClient\Exceptions\ApiException; +use D3\LinkmobilityClient\Request\RequestInterface; +use D3\LinkmobilityClient\Response\Response; +use D3\LinkmobilityClient\Response\ResponseInterface; +use D3\LinkmobilityClient\SMS\TextRequest; +use D3\LinkmobilityClient\Url; +use D3\LinkmobilityClient\UrlInterface; +use GuzzleHttp\Client as GuzzleClient; +use GuzzleHttp\ClientInterface; +use PHPUnit\Framework\MockObject\MockObject; +use Psr\Http\Message\ResponseInterface as MessageResponseInterface; +use Psr\Http\Message\StreamInterface; +use Psr\Log\AbstractLogger; +use Psr\Log\LoggerInterface; +use ReflectionException; -class ApiClientTest extends ApiTestCase +class ClientTest extends ApiTestCase { public string $fixtureApiKey = 'fixtureApiKey'; - /** @var ApiClient */ + /** @var Client */ public Client $api; public string $jsonFixture = 'json_content'; + /** + * @return void + */ public function setUp():void { parent::setUp(); $this->api = new Client($this->fixtureApiKey); } - + + /** + * @return void + */ + public function tearDown(): void + { + parent::tearDown(); + + unset($this->api); + } + /** * @test + * @dataProvider constructPassedArgumentsDataProvider + * @param $apiKey + * @param $apiUrl + * @param $apiClient + * @return void + * @throws ReflectionException */ - public function testRun() + public function testConstruct($apiKey, $apiUrl, $apiClient) { - $this->assertTrue(true); + $api = new Client($apiKey, $apiUrl, $apiClient); + + $this->assertSame( + $this->getValue($api, 'accessToken'), + $apiKey + ); + $this->assertInstanceOf( + UrlInterface::class, + $this->getValue($api, 'apiUrl') + ); + $this->assertInstanceOf( + ClientInterface::class, + $this->getValue($api, 'requestClient') + ); + } + + /** + * @return array[] + */ + public function constructPassedArgumentsDataProvider(): array + { + return [ + 'api key only' => ['apiKey', null, null], + 'all without client' => ['apiKey', new Url(), null], + 'all arguments' => ['apiKey', new Url(), new GuzzleClient()] + ]; + } + + /** + * @test + * @return void + * @throws ReflectionException + * @dataProvider requestPassedDataProvider + * + */ + public function testRequest($requestIsValid) + { + /** @var Client|MockObject apiMock */ + $apiMock = $this->getMockBuilder(Client::class) + ->onlyMethods(['rawRequest']) + ->disableOriginalConstructor() + ->getMock(); + $apiMock->expects($this->exactly((int) $requestIsValid))->method('rawRequest'); + + /** @var ResponseInterface|MockObject $responseMock */ + $responseMock = $this->getMockBuilder(Response::class) + ->disableOriginalConstructor() + ->getMock(); + + /** @var RequestInterface|MockObject $requestMock */ + $requestMock = $this->getMockBuilder(TextRequest::class) + ->disableOriginalConstructor() + ->onlyMethods(['validate', 'getResponseInstance', 'getUri', 'getMethod', 'getOptions']) + ->getMock(); + + /** @var InvalidArgumentException|MockObject $invalidArgExceptionMock */ + $invalidArgExceptionMock = $this->getMockBuilder(InvalidArgumentException::class) + ->disableOriginalConstructor() + ->getMock(); + + if ($requestIsValid) { + $requestMock->expects($this->atLeastOnce())->method('validate')->willReturn(true); + } else { + $requestMock->expects($this->atLeastOnce())->method('validate') + ->willThrowException($invalidArgExceptionMock); + } + $requestMock->expects($this->exactly((int) $requestIsValid)) + ->method('getResponseInstance')->willReturn($responseMock); + $requestMock->expects($this->exactly((int) $requestIsValid)) + ->method('getUri')->willReturn('fixtureUrl'); + $requestMock->expects($this->exactly((int) $requestIsValid)) + ->method('getMethod')->willReturn(RequestInterface::METHOD_GET); + $requestMock->expects($this->exactly((int) $requestIsValid)) + ->method('getOptions')->willReturn([]); + + $this->api = $apiMock; + + if (false === $requestIsValid) { + $this->expectException(InvalidArgumentException::class); + } + + $this->assertSame( + $responseMock, + $this->callMethod($this->api, 'request', [$requestMock]) + ); + } + + /** + * @return array + */ + public function requestPassedDataProvider(): array + { + return [ + 'request is valid' => [true], + 'request is not valid' => [false] + ]; + } + + /** + * @test + * @param $useLogger + * @param $okStatus + * @return void + * @throws ReflectionException + * @dataProvider rawRequestDataProvider + */ + public function testRawRequest($useLogger, $okStatus) + { + $statusCode = $okStatus ? '200' : '301'; + + /** @var StreamInterface|MockObject $streamMock */ + $streamMock = $this->getMockBuilder(StreamInterface::class) + ->getMock(); + + /** @var MessageResponseInterface|MockObject $responseMock */ + $responseMock = $this->getMockBuilder(MessageResponseInterface::class) + ->onlyMethods([ + 'getStatusCode', + 'getBody', + 'withStatus', + 'getReasonPhrase', + 'getProtocolVersion', + 'withProtocolVersion', + 'getHeaders', + 'hasHeader', + 'getHeader', + 'getHeaderLine', + 'withHeader', + 'withAddedHeader', + 'withoutHeader', + 'withBody' + ]) + ->disableOriginalConstructor() + ->getMock(); + $responseMock->expects($this->atLeastOnce())->method('getStatusCode')->willReturn($statusCode); + $responseMock->expects($useLogger && $okStatus ? $this->atLeastOnce() : $this->never()) + ->method('getBody')->willReturn($streamMock); + + /** @var GuzzleClient|MockObject $requestClientMock */ + $requestClientMock = $this->getMockBuilder(GuzzleClient::class) + ->onlyMethods(['request']) + ->getMock(); + $requestClientMock->expects($this->once())->method('request')->willReturn($responseMock); + + /** @var LoggerInterface|MockObject $loggerMock */ + $loggerMock = $this->getMockBuilder(AbstractLogger::class) + ->onlyMethods(['debug', 'error', 'log']) + ->getMock(); + + /** @var Client|MockObject $clientMock */ + $clientMock = $this->getMockBuilder(Client::class) + ->disableOriginalConstructor() + ->onlyMethods([ + 'hasLogger', + 'getLogger' + ]) + ->getMock(); + $clientMock->method('hasLogger')->willReturn($useLogger); + $clientMock->expects($useLogger ? $this->atLeastOnce() : $this->never()) + ->method('getLogger')->willReturn($loggerMock); + $this->setValue($clientMock, 'requestClient', $requestClientMock); + + if (false === $okStatus) { + $this->expectException(ApiException::class); + } + $this->assertSame( + $responseMock, + $this->callMethod($clientMock, 'rawRequest', ['myUrl']) + ); + } + + /** + * @return array + */ + public function rawRequestDataProvider(): array + { + return [ + 'has logger, OK status' => [true, true], + 'has no logger, OK status' => [false, true], + 'has logger, NOK status' => [true, false], + 'has no logger, NOK status' => [false, false], + ]; + } + + /** + * @test + * @return void + * @throws ReflectionException + */ + public function testLogger() + { + $this->assertFalse($this->callMethod($this->api, 'hasLogger')); + $this->assertNull($this->callMethod($this->api, 'getLogger')); + + /** @var LoggerInterface|MockObject $loggerMock */ + $loggerMock = $this->getMockBuilder(AbstractLogger::class) + ->onlyMethods(['debug', 'error', 'log']) + ->getMock(); + + $this->assertSame( + $this->api, + $this->callMethod($this->api, 'setLogger', [$loggerMock]) + ); + + $this->assertTrue($this->callMethod($this->api, 'hasLogger')); + $this->assertSame( + $loggerMock, + $this->callMethod($this->api, 'getLogger') + ); } } diff --git a/Tests/README.md b/Tests/README.md index 656b7db..97097b8 100644 --- a/Tests/README.md +++ b/Tests/README.md @@ -1,7 +1,7 @@ # Installation ``` -composer create-project -s dev --prefer-source --repository="{\"url\": \"gitfhfac@git.d3data.de:D3Private/linkmobility-php-client.git\", \"type\": \"vcs\"}" d3/linkmobility-php-client . +composer create-project -s dev --prefer-source d3/linkmobility-php-client . ``` # Run tests diff --git a/Tests/UrlTest.php b/Tests/UrlTest.php new file mode 100644 index 0000000..c08b710 --- /dev/null +++ b/Tests/UrlTest.php @@ -0,0 +1,63 @@ + + * @link http://www.oxidmodule.com + */ + +declare( strict_types = 1 ); + +namespace D3\LinkmobilityClient\Tests; + +use D3\LinkmobilityClient\Url; +use ReflectionException; + +class UrlTest extends ApiTestCase +{ + /** @var Url */ + public Url $url; + + /** + * @return void + */ + public function setUp():void + { + parent::setUp(); + + $this->url = new Url(); + } + + public function tearDown(): void + { + parent::tearDown(); + + unset($this->url); + } + + /** + * @test + * @return void + * @throws ReflectionException + */ + public function testGetBaseUri() + { + $fixture = "fixtureUri"; + $this->setValue($this->url, 'baseUri', $fixture); + + $this->assertSame( + $fixture, + $this->callMethod( + $this->url, + 'getBaseUri' + ) + ); + } +} diff --git a/src/Client.php b/src/Client.php index bfae017..ac57166 100644 --- a/src/Client.php +++ b/src/Client.php @@ -20,13 +20,11 @@ namespace D3\LinkmobilityClient; use D3\LinkmobilityClient\Exceptions\ApiException; use D3\LinkmobilityClient\Exceptions\ExceptionMessages; use D3\LinkmobilityClient\Request\RequestInterface; -use D3\LinkmobilityClient\ValueObject\ValueObject; +use GuzzleHttp\ClientInterface; use GuzzleHttp\Exception\GuzzleException; use InvalidArgumentException; -use phpDocumentor\Reflection\Types\Mixed_; use Psr\Http\Message\ResponseInterface; use Psr\Log\LoggerInterface; -use RuntimeException; class Client { @@ -35,14 +33,9 @@ class Client public $requestClient; private $logger; - private $configuration = []; - public function __construct(string $accessToken, $apiUrl = false, $client = false) + public function __construct(string $accessToken, UrlInterface $apiUrl = null, ClientInterface $client = null) { - if ($apiUrl !== false && false === $apiUrl instanceof UrlInterface) { - throw new RuntimeException(ExceptionMessages::WRONG_APIURL_INTERFACE); - } - $this->accessToken = $accessToken; $this->apiUrl = $apiUrl ?: new Url(); $this->requestClient = $client ?: new \GuzzleHttp\Client( [ 'base_uri' => $this->apiUrl->getBaseUri() ] ); @@ -87,7 +80,7 @@ class Client ); if ($response->getStatusCode() != 200) { - $message = sprintf(ExceptionMessages::NOK_REQUEST_RETURN, [$url, $response->getStatusCode()]); + $message = sprintf(ExceptionMessages::NOK_REQUEST_RETURN, $url, $response->getStatusCode()); if ($this->hasLogger()) $this->getLogger()->error($message); throw new ApiException($message); } @@ -123,40 +116,8 @@ class Client /** * @return LoggerInterface */ - public function getLogger(): LoggerInterface + public function getLogger(): ?LoggerInterface { - return $this->logger; - } - - /** - * @param string $name - * @param $configuration - * - * @return $this - */ - public function setConfiguration( string $name, $configuration ): Client - { - $this->configuration[$name] = oxNew(ValueObject::class, $configuration); - - return $this; - } - - public function hasConfiguration(string $name) - { - return isset($this->configuration[$name]); - } - - /** - * @param string $name - * - * @return mixed - */ - public function getConfiguration(string $name) - { - if (false === isset($this->configuration)) { - throw new InvalidArgumentException('configuration '.$name.' is not set'); - } - - return $this->configuration[$name]; + return $this->hasLogger() ? $this->logger : null; } } \ No newline at end of file diff --git a/src/Exceptions/ExceptionMessages.php b/src/Exceptions/ExceptionMessages.php index 79e511a..c8bcaa7 100644 --- a/src/Exceptions/ExceptionMessages.php +++ b/src/Exceptions/ExceptionMessages.php @@ -19,8 +19,6 @@ class ExceptionMessages { const INVALID_SENDER = 'invalid sender phone number'; - const WRONG_APIURL_INTERFACE = 'ApiUrl instance must implement UrlInterface'; - const NOK_REQUEST_RETURN = 'request %1$s returns status code %2$s'; const INVALID_RECIPIENT_PHONE = 'invalid recipient phone number';