fix(балансы): свежий query-builder на итерацию джобы (PK violation на 2-м прогоне)

Переиспользование одного DB-билдера в цикле накапливало where-клаузы →
updateOrInsert уходил в INSERT существующей строки → SQLSTATE 23505 на проде
при повторном сборе. Билдер теперь создаётся внутри цикла. + тест на 2 прогона.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Дмитрий
2026-06-28 07:32:17 +03:00
parent c03e2b319b
commit fa404e98ec
3 changed files with 22 additions and 9 deletions
@@ -51,6 +51,18 @@ it('пишет балансы трёх сервисов + считает све
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)));
(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)));