1fef2571e8
email — денежный сервис; сумма вписывается в админке «Система» (Yandex360BalanceStore), светофор по порогам, кнопка «Открыть оплату»/«Пополнить» → admin.yandex.ru/products. Робот-скрейпер отклонён (SPA Яндекса враждебен ботам + автопополнение защищает баланс). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
104 lines
6.4 KiB
PHP
104 lines
6.4 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Jobs\External\RefreshExternalBalancesJob;
|
|
use App\Services\External\BalanceReading;
|
|
use App\Services\External\DadataBalanceProvider;
|
|
use App\Services\External\LivenessReading;
|
|
use App\Services\External\SupplierBalanceProvider;
|
|
use App\Services\External\Yandex360BalanceProvider;
|
|
use App\Services\External\YandexCloudBalanceProvider;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class); // запись идёт через pgsql_supplier
|
|
|
|
beforeEach(function () {
|
|
Mail::fake(); // этот файл про запись балансов, не про письма (алерт — в ExternalServiceDownAlertTest)
|
|
// email — денежный сервис (Yandex 360). Дефолтный стаб (зелёный, выше порогов); тесты могут переопределить.
|
|
app()->instance(
|
|
Yandex360BalanceProvider::class,
|
|
fakeProvider('email', BalanceReading::ok('email', 5000, 'RUB', null)),
|
|
);
|
|
});
|
|
|
|
afterEach(fn () => RefreshExternalBalancesJob::resetLivenessProbes());
|
|
|
|
// Стабы fakeProvider()/fakeProbe() — глобальные хелперы в tests/Pest.php.
|
|
|
|
it('пишет балансы трёх сервисов + считает светофор', function () {
|
|
config()->set('services.yandex_cloud.red_floor_rub', 1000);
|
|
config()->set('services.yandex_cloud.amber_floor_rub', 5000);
|
|
|
|
app()->instance(DadataBalanceProvider::class, fakeProvider('dadata', BalanceReading::ok('dadata', 4500, 'RUB', 100)));
|
|
app()->instance(SupplierBalanceProvider::class, fakeProvider('supplier', BalanceReading::ok('supplier', 50000, 'RUB', null)));
|
|
app()->instance(YandexCloudBalanceProvider::class, fakeProvider('yandex_cloud', BalanceReading::ok('yandex_cloud', -540.48, 'RUB', 600)));
|
|
RefreshExternalBalancesJob::useLivenessProbes([]); // этот тест — только про балансы
|
|
|
|
(new RefreshExternalBalancesJob)->handle();
|
|
|
|
$rows = DB::connection('pgsql_supplier')->table('external_service_balances')->get()->keyBy('service_key');
|
|
expect($rows)->toHaveCount(4); // dadata/supplier/yandex_cloud + email (денежный)
|
|
expect((float) $rows['yandex_cloud']->balance_amount)->toBe(-540.48);
|
|
expect($rows['yandex_cloud']->light)->toBe('red'); // минус < red_floor
|
|
expect((bool) $rows['yandex_cloud']->ok)->toBeTrue();
|
|
expect($rows['dadata']->ok)->toBeTruthy();
|
|
});
|
|
|
|
it('повторный запуск обновляет строки, а не падает на PK (свежий builder/итерация)', function () {
|
|
app()->instance(DadataBalanceProvider::class, fakeProvider('dadata', BalanceReading::ok('dadata', 4500, 'RUB', 100)));
|
|
app()->instance(SupplierBalanceProvider::class, fakeProvider('supplier', BalanceReading::fail('supplier', 'таймаут')));
|
|
app()->instance(YandexCloudBalanceProvider::class, fakeProvider('yandex_cloud', BalanceReading::ok('yandex_cloud', 42000, 'RUB', 600)));
|
|
RefreshExternalBalancesJob::useLivenessProbes([]); // этот тест — только про балансы
|
|
|
|
(new RefreshExternalBalancesJob)->handle();
|
|
(new RefreshExternalBalancesJob)->handle(); // второй прогон не должен бросить UniqueConstraint
|
|
|
|
$rows = DB::connection('pgsql_supplier')->table('external_service_balances')->get();
|
|
expect($rows)->toHaveCount(4); // dadata/supplier/yandex_cloud + email, без дублей
|
|
});
|
|
|
|
it('упавший провайдер не роняет джобу и сохраняет ошибку, остальные пишутся', function () {
|
|
app()->instance(DadataBalanceProvider::class, fakeProvider('dadata', BalanceReading::fail('dadata', 'HTTP 403')));
|
|
app()->instance(SupplierBalanceProvider::class, fakeProvider('supplier', BalanceReading::ok('supplier', 50000, 'RUB', null)));
|
|
app()->instance(YandexCloudBalanceProvider::class, fakeProvider('yandex_cloud', BalanceReading::ok('yandex_cloud', 42000, 'RUB', 600)));
|
|
RefreshExternalBalancesJob::useLivenessProbes([]); // этот тест — только про балансы
|
|
|
|
(new RefreshExternalBalancesJob)->handle();
|
|
|
|
$rows = DB::connection('pgsql_supplier')->table('external_service_balances')->get()->keyBy('service_key');
|
|
expect((bool) $rows['dadata']->ok)->toBeFalse();
|
|
expect($rows['dadata']->error)->toContain('403');
|
|
expect((bool) $rows['supplier']->ok)->toBeTrue();
|
|
expect((bool) $rows['yandex_cloud']->ok)->toBeTrue();
|
|
});
|
|
|
|
it('пишет строки живости: balance_amount NULL, цвет из пробы', function () {
|
|
// Балансовые провайдеры — заглушки-ок, чтобы не ходить в сеть.
|
|
app()->instance(DadataBalanceProvider::class, fakeProvider('dadata', BalanceReading::ok('dadata', 4500, 'RUB', 100)));
|
|
app()->instance(SupplierBalanceProvider::class, fakeProvider('supplier', BalanceReading::ok('supplier', 50000, 'RUB', null)));
|
|
app()->instance(YandexCloudBalanceProvider::class, fakeProvider('yandex_cloud', BalanceReading::ok('yandex_cloud', 42000, 'RUB', 600)));
|
|
// email — денежный (переопределяем дефолтный стаб из beforeEach конкретной суммой).
|
|
app()->instance(Yandex360BalanceProvider::class, fakeProvider('email', BalanceReading::ok('email', 777, 'RUB', null)));
|
|
|
|
RefreshExternalBalancesJob::useLivenessProbes([
|
|
fakeProbe('jivosite', LivenessReading::down('jivosite', 'HTTP 500')),
|
|
fakeProbe('captcha', LivenessReading::unknown('captcha', 'выключена')),
|
|
]);
|
|
|
|
(new RefreshExternalBalancesJob)->handle();
|
|
|
|
$rows = DB::connection('pgsql_supplier')->table('external_service_balances')->get()->keyBy('service_key');
|
|
expect($rows)->toHaveCount(6); // 4 деньги (вкл. email) + 2 живость
|
|
expect((float) $rows['email']->balance_amount)->toBe(777.0);
|
|
expect((bool) $rows['email']->ok)->toBeTrue();
|
|
expect($rows['jivosite']->light)->toBe('red');
|
|
expect((bool) $rows['jivosite']->ok)->toBeTrue(); // ok=true: статус свежий и определённый (упал)
|
|
expect($rows['jivosite']->error)->toContain('500');
|
|
expect($rows['captcha']->light)->toBe('grey');
|
|
expect((bool) $rows['captcha']->ok)->toBeFalse(); // grey = не смогли/не применимо
|
|
});
|