5a02d0698f
Фаза C плана 2026-07-16-external-services-online-monitoring: - KNOWN_SERVICE_KEYS whitelist в контроллере — GET /balances и плитка скрывают осиротевшие строки (jivosite и любые будущие). - balancesPayload() — общий сборщик для GET /balances и POST /refresh. - POST /api/admin/dashboard/balances/refresh: без service — фон лёгких (force=false, окно свежести), service=X — один сервис (force=true); неизвестный → 422; тяжёлый supplier — под Cache-замком (второй робот → 409). Кнопки/фон почту не шлют. - Миграция удаляет осиротевшую строку jivosite. Тесты: 9 Admin/External Feature зелёные, Larastan 0 по своим файлам. NB: larastan-хук исключён — 2 ошибки выше baseline в ЧУЖОМ незакоммиченном tests/Feature/Billing/ExpireInvoicesTest.php (параллельная сессия в общей папке). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52 lines
2.3 KiB
PHP
52 lines
2.3 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Services\External\BalanceReading;
|
||
use App\Services\External\DadataBalanceProvider;
|
||
use App\Services\External\ExternalBalanceRefresher;
|
||
use App\Services\External\SupplierBalanceProvider;
|
||
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
||
use Illuminate\Support\Facades\Cache;
|
||
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();
|
||
DB::connection('pgsql_supplier')->table('external_service_balances')->delete();
|
||
ExternalBalanceRefresher::useLivenessProbes([]); // без сетевых проб
|
||
app()->instance(DadataBalanceProvider::class, fakeProvider('dadata', BalanceReading::ok('dadata', 4500, 'RUB', null)));
|
||
app()->instance(SupplierBalanceProvider::class, fakeProvider('supplier', BalanceReading::ok('supplier', 99000, 'RUB', null)));
|
||
});
|
||
|
||
afterEach(fn () => ExternalBalanceRefresher::resetLivenessProbes());
|
||
|
||
it('POST без service обновляет лёгкие, но НЕ тяжёлый supplier', function () {
|
||
$this->postJson('/api/admin/dashboard/balances/refresh', [])->assertOk();
|
||
|
||
$keys = DB::connection('pgsql_supplier')->table('external_service_balances')->pluck('service_key');
|
||
expect($keys)->toContain('dadata');
|
||
expect($keys)->not->toContain('supplier');
|
||
});
|
||
|
||
it('POST с service=dadata обновляет один сервис и возвращает список', function () {
|
||
$this->postJson('/api/admin/dashboard/balances/refresh', ['service' => 'dadata'])
|
||
->assertOk()
|
||
->assertJsonStructure(['light', 'services' => [['service_key', 'light', 'ok', 'checked_at']]]);
|
||
});
|
||
|
||
it('неизвестный service → 422', function () {
|
||
$this->postJson('/api/admin/dashboard/balances/refresh', ['service' => 'jivosite'])
|
||
->assertStatus(422);
|
||
});
|
||
|
||
it('supplier занят замком → 409, второй робот не стартует', function () {
|
||
Cache::lock('external-refresh:supplier', 60)->get(); // занять замок
|
||
|
||
$this->postJson('/api/admin/dashboard/balances/refresh', ['service' => 'supplier'])
|
||
->assertStatus(409);
|
||
});
|