Bicycle/tests/Unit/CurrencyRateClientTest.php
2026-08-15 19:40:29 +03:00

117 lines
4.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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'));
}
}