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(3); 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(3); // строк по-прежнему 3, без дублей }); 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))); RefreshExternalBalancesJob::useLivenessProbes([ fakeProbe('email', LivenessReading::alive('email', 'SMTP отвечает')), 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); // 3 деньги + 3 живость expect($rows['email']->balance_amount)->toBeNull(); expect($rows['email']->light)->toBe('green'); 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 = не смогли/не применимо });