117 lines
4.6 KiB
PHP
117 lines
4.6 KiB
PHP
<?php
|
||
|
||
namespace Tests\Unit;
|
||
|
||
use PHPUnit\Framework\TestCase;
|
||
use Services\CurrencyRate\Client;
|
||
|
||
/**
|
||
* Сеть ЦБ РФ недоступна из dev-контейнера (изолированная песочница) — fetch() гарантированно
|
||
* уходит в graceful null, это и проверяем (см. Services\CurrencyRate\Client::fetch()). Кэш
|
||
* проверяется отдельно, через подложенный файл, без реального похода в сеть.
|
||
*/
|
||
class CurrencyRateClientTest extends TestCase
|
||
{
|
||
private string $cache_path;
|
||
|
||
protected function setUp(): void
|
||
{
|
||
$this->cache_path = sys_get_temp_dir() . '/currency_rate_test_' . uniqid() . '.json';
|
||
}
|
||
|
||
protected function tearDown(): void
|
||
{
|
||
if (is_file($this->cache_path)) {
|
||
unlink($this->cache_path);
|
||
}
|
||
|
||
if (is_file($this->cache_path . '.failed')) {
|
||
unlink($this->cache_path . '.failed');
|
||
}
|
||
}
|
||
|
||
public function testRubAlwaysReturnsOne(): void
|
||
{
|
||
$client = new Client(['url' => 'https://unreachable.invalid/', 'timeout' => 1, 'cache_path' => $this->cache_path]);
|
||
|
||
$this->assertSame(1.0, $client->rate('rub'));
|
||
$this->assertSame(1.0, $client->rate('RUB'));
|
||
}
|
||
|
||
public function testUnreachableSourceReturnsNullWithoutThrowing(): void
|
||
{
|
||
$client = new Client(['url' => 'https://unreachable.invalid/', 'timeout' => 1, 'cache_path' => $this->cache_path]);
|
||
|
||
$this->assertNull($client->rate('EUR'));
|
||
}
|
||
|
||
public function testReadsFromFreshCacheWithoutNetwork(): void
|
||
{
|
||
file_put_contents($this->cache_path, json_encode(['EUR' => 104.5]));
|
||
|
||
// url заведомо невалиден — если бы клиент реально пошёл в сеть, тест бы не прошёл (null).
|
||
$client = new Client(['url' => 'not-a-url', 'timeout' => 1, 'cache_path' => $this->cache_path, 'ttl' => 86400]);
|
||
|
||
$this->assertSame(104.5, $client->rate('EUR'));
|
||
}
|
||
|
||
public function testUnknownCodeReturnsNull(): void
|
||
{
|
||
file_put_contents($this->cache_path, json_encode(['EUR' => 104.5]));
|
||
|
||
$client = new Client(['url' => 'not-a-url', 'timeout' => 1, 'cache_path' => $this->cache_path, 'ttl' => 86400]);
|
||
|
||
$this->assertNull($client->rate('XXX'));
|
||
}
|
||
|
||
/**
|
||
* Неудачный fetch() (сеть/ЦБ недоступны) должен оставить .failed-маркер — см. Client::rates().
|
||
* fetch() замокан, а не реально бьёт в сеть: тайминг на "недоступном" хосте ненадёжен для
|
||
* теста (DNS-отказ мгновенный, а реальный таймаут — секунды, в зависимости от окружения).
|
||
*/
|
||
public function testFailedFetchWritesFailureMarker(): void
|
||
{
|
||
$client = $this->getMockBuilder(Client::class)
|
||
->setConstructorArgs([['cache_path' => $this->cache_path]])
|
||
->onlyMethods(['fetch'])
|
||
->getMock();
|
||
$client->method('fetch')->willReturn(null);
|
||
|
||
$client->rate('EUR');
|
||
|
||
$this->assertFileExists($this->cache_path . '.failed');
|
||
}
|
||
|
||
/**
|
||
* Без этого кулдауна каждый следующий запрос (например, каждая загрузка дашборда, пока ЦБ
|
||
* недоступен) заново ждал бы полный timeout — см. Client::rates(). Проверяем не временем
|
||
* (ненадёжно), а тем, что fetch() вообще не вызывается повторно, пока действует недавний
|
||
* .failed-маркер.
|
||
*/
|
||
public function testFailureCooldownSkipsFetch(): void
|
||
{
|
||
touch($this->cache_path . '.failed');
|
||
|
||
$client = $this->getMockBuilder(Client::class)
|
||
->setConstructorArgs([['cache_path' => $this->cache_path, 'fail_cooldown' => 300]])
|
||
->onlyMethods(['fetch'])
|
||
->getMock();
|
||
$client->expects($this->never())->method('fetch');
|
||
|
||
$this->assertNull($client->rate('EUR'));
|
||
}
|
||
|
||
public function testFetchIsRetriedAfterCooldownExpires(): void
|
||
{
|
||
touch($this->cache_path . '.failed', time() - 10);
|
||
|
||
$client = $this->getMockBuilder(Client::class)
|
||
->setConstructorArgs([['cache_path' => $this->cache_path, 'fail_cooldown' => 5]])
|
||
->onlyMethods(['fetch'])
|
||
->getMock();
|
||
$client->expects($this->once())->method('fetch')->willReturn(null);
|
||
|
||
$this->assertNull($client->rate('EUR'));
|
||
}
|
||
}
|