From 9148833ab7e8e658f1d9d999eb617ad436dd3b16 Mon Sep 17 00:00:00 2001 From: mgcodeur Date: Tue, 30 Jan 2024 16:20:06 +0300 Subject: [PATCH] Adding custom exception handling && Adding missing tests --- src/CurrencyConverter.php | 12 ++- src/Exceptions/MissingAmountException.php | 10 ++ src/Exceptions/MissingCurrencyException.php | 10 ++ src/Exceptions/NetworkException.php | 10 ++ src/Services/CurrencyService.php | 8 +- src/Traits/CurrencyConverterManager.php | 16 +-- tests/Unit/CurrencyConverterTest.php | 111 ++++++++++++++++++-- 7 files changed, 151 insertions(+), 26 deletions(-) create mode 100644 src/Exceptions/MissingAmountException.php create mode 100644 src/Exceptions/MissingCurrencyException.php create mode 100644 src/Exceptions/NetworkException.php diff --git a/src/CurrencyConverter.php b/src/CurrencyConverter.php index be974a4..8d8cf9b 100644 --- a/src/CurrencyConverter.php +++ b/src/CurrencyConverter.php @@ -2,7 +2,7 @@ namespace Mgcodeur\CurrencyConverter; -use Exception; +use Mgcodeur\CurrencyConverter\Exceptions\NetworkException; use Mgcodeur\CurrencyConverter\Services\CurrencyService; use Mgcodeur\CurrencyConverter\Traits\CurrencyConverterManager; @@ -25,7 +25,7 @@ public function __construct(private CurrencyService $currencyService) /** * @return $this */ - public function convert(float $amount): static + public function convert(float $amount = 0): static { $this->amount = $amount; @@ -35,7 +35,7 @@ public function convert(float $amount): static /** * @return $this */ - public function amount(float $amount): static + public function amount(float $amount = 0): static { $this->amount = $amount; @@ -63,7 +63,9 @@ public function to(string $to): static } /** - * @throws Exception + * @return $this + * + * @throws NetworkException */ public function currencies(): static { @@ -71,7 +73,7 @@ public function currencies(): static $result = $response->json(); if (! $result) { - throw new Exception('Something went wrong, please try again later'); + throw new NetworkException(); } $this->currencies = array_change_key_case($result, CASE_UPPER); diff --git a/src/Exceptions/MissingAmountException.php b/src/Exceptions/MissingAmountException.php new file mode 100644 index 0000000..a44edd2 --- /dev/null +++ b/src/Exceptions/MissingAmountException.php @@ -0,0 +1,10 @@ +baseUrl.'.json'); } - public function runConversionFrom(string $from, ?string $to = null): Response + public function runConversionFrom(string $from, ?string $to = ''): Response { - if (! $to) { - return Http::get($this->baseUrl."/{$from}.json"); - } - - return Http::get($this->baseUrl."/{$from}/{$to}.json"); + return $to ? Http::get($this->baseUrl."/{$from}/{$to}.json") : Http::get($this->baseUrl."/{$from}.json"); } } diff --git a/src/Traits/CurrencyConverterManager.php b/src/Traits/CurrencyConverterManager.php index bcf3a98..36ade9e 100644 --- a/src/Traits/CurrencyConverterManager.php +++ b/src/Traits/CurrencyConverterManager.php @@ -2,12 +2,14 @@ namespace Mgcodeur\CurrencyConverter\Traits; -use Exception; +use Mgcodeur\CurrencyConverter\Exceptions\MissingAmountException; +use Mgcodeur\CurrencyConverter\Exceptions\MissingCurrencyException; +use Mgcodeur\CurrencyConverter\Exceptions\NetworkException; trait CurrencyConverterManager { /** - * @throws Exception + * @throws NetworkException | MissingAmountException | MissingCurrencyException */ public function get($format = false): float|int|array|string { @@ -23,7 +25,7 @@ public function get($format = false): float|int|array|string ); if ($response->failed() || ! $response->json()) { - throw new Exception('Something went wrong, please try again later'); + throw new NetworkException(); } $result = $response->json(); @@ -50,7 +52,7 @@ public function get($format = false): float|int|array|string } /** - * @throws Exception + * @throws NetworkException | MissingAmountException | MissingCurrencyException */ public function format(): float|array|int|string { @@ -58,15 +60,15 @@ public function format(): float|array|int|string } /** - * @throws Exception + * @throws MissingAmountException | MissingCurrencyException */ private function verifyDataBeforeGettingResults(): void { if (! $this->amount && ! $this->currencies) { - throw new Exception('Amount is required, please use convert() or amount() method before getting the result'); + throw new MissingAmountException(); } if (! $this->from && ! $this->currencies) { - throw new Exception('From currency is required, please specify currency using from() method before getting the result'); + throw new MissingCurrencyException(); } } } diff --git a/tests/Unit/CurrencyConverterTest.php b/tests/Unit/CurrencyConverterTest.php index 48f4793..579db58 100644 --- a/tests/Unit/CurrencyConverterTest.php +++ b/tests/Unit/CurrencyConverterTest.php @@ -27,27 +27,122 @@ expect($currencyConverter->to('USD'))->toBeInstanceOf(CurrencyConverter::class); }); -test('get conversion without setting amount throws exception', function () { +test('throws MissingAmountException when getting conversion without setting amount', function () { $currencyConverter = app(CurrencyConverter::class); $currencyConverter->get(); -})->throws(Exception::class); +})->throws(\Mgcodeur\CurrencyConverter\Exceptions\MissingAmountException::class); + +test('throw MissingCurrencyException if we don\'t specify from currency', function () { + $currencyConverter = app(CurrencyConverter::class); + + $currencyConverter + ->convert(10) + ->to('usd') + ->get(); +})->throws(\Mgcodeur\CurrencyConverter\Exceptions\MissingCurrencyException::class); test('convert currency from USD to EUR', function () { - $mockResponse = new \Illuminate\Http\Client\Response( - new \GuzzleHttp\Psr7\Response(200, [], json_encode(['eur' => 2000.0])) + // example of value of 1 USD in EUR + $dataToMock = [ + 'eur' => 0.92326769, + ]; + + $amountToConvert = 100; + + $convertedAmount = 0.92326769 * $amountToConvert; + + $mockedResponse = new \Illuminate\Http\Client\Response( + new \GuzzleHttp\Psr7\Response(200, [], json_encode($dataToMock)) ); $this->currencyServiceMock ->shouldReceive('runConversionFrom') ->with('usd', 'eur') - ->andReturn($mockResponse); + ->andReturn($mockedResponse); + + $currencyConverter = app(CurrencyConverter::class); + $convertedAmount = $currencyConverter->from('usd') + ->to('eur') + ->convert($amountToConvert) + ->get(); + + expect($convertedAmount)->toBeFloat()->toEqual($convertedAmount); +}); + +test('convert currency from USD to all supported currencies', function () { + // example of value of 1 USD in [eur, mga, btc] + $dataToMock = [ + 'eur' => 0.92326769, + 'mga' => 4511.5880329, + 'btc' => 0.0000229836, + ]; + + $amountToConvert = 100; + + $convertedAmount = array_map(fn ($value) => $value * $amountToConvert, $dataToMock); + + $mockedResponse = new \Illuminate\Http\Client\Response( + new \GuzzleHttp\Psr7\Response(200, [], json_encode($dataToMock)) + ); + + $this->currencyServiceMock + ->shouldReceive('runConversionFrom') + ->with('usd', Mockery::any()) + ->andReturn($mockedResponse); + + $this->currencyServiceMock + ->shouldReceive('convertAllCurrency') + ->with( + 100, + 'usd', + $dataToMock, + false + ) + ->andReturn($convertedAmount); $currencyConverter = app(CurrencyConverter::class); - $convertedAmount = $currencyConverter->from('USD') - ->to('EUR') + + $testResult = $currencyConverter->from('usd') ->convert(100) ->get(); - expect($convertedAmount)->toBeFloat()->toEqual(2000.0 * 100); + expect($testResult)->toBeArray()->toEqual($convertedAmount); +}); + +test('fetch a list of all supported currencies', function () { + $dataToMock = [ + '1000SATS' => '', + '1INCH' => '1inch', + 'AAVE' => 'Aave', + 'MGA' => 'Malagasy Ariary', + ]; + + $mockedResponse = new \Illuminate\Http\Client\Response( + new \GuzzleHttp\Psr7\Response(200, [], json_encode($dataToMock)) + ); + + $this->currencyServiceMock + ->shouldReceive('fetchAllCurrencies') + ->andReturn($mockedResponse); + + $currencyConverter = app(CurrencyConverter::class); + + $testResult = $currencyConverter->currencies()->get(); + + expect($testResult)->toBeArray()->toEqual($dataToMock); }); + +test('throw NetworkException when fetching a list of all supported currencies has no response or fail', function () { + $mockedResponse = new \Illuminate\Http\Client\Response( + new \GuzzleHttp\Psr7\Response(500, [], '') + ); + + $this->currencyServiceMock + ->shouldReceive('fetchAllCurrencies') + ->andReturn($mockedResponse); + + $currencyConverter = app(CurrencyConverter::class); + + $currencyConverter->currencies()->get(); +})->throws(\Mgcodeur\CurrencyConverter\Exceptions\NetworkException::class);