Files
portal/app/tests/Feature/External/ExternalServiceDownAlertTest.php
T
Дмитрий d9f22d1353 fix тесты: убрана грязь между тестами и закрыт выход в интернет из прогона
Полный набор стал зелёным целиком: 4063 теста, 0 падений и 0 ошибок против
36 непроходящих до правок. Рабочий код не тронут - изменены только тесты.

Корень у пяти правок один: тест зелёный, а после себя оставляет мусор в базе,
и падают соседи. Три файла рекламы писали набело, без отката. Их записи -
задание роботу и проекты без источника лидов - переживали тест, и дальше
в том же прогоне их подбирали чужие проверки. Отсюда 16 падений в файле
про робота креативов и 13 в файлах про ночной слепок.

Тестам запрещён выход в интернет - Http::preventStrayRequests в TestCase.
До этого прогон физически ходил в живой кабинет Яндекс Директа, а поломка
маскировалась под "Invalid OAuth token", хотя ключ в тесте подставной.
Сторож принят вырезанием: со снятой правкой та же поломка называет себя
честно, с адресом запроса.

Он же вскрыл, что ProjectRuleNotificationTest слал живой запрос на удаление
проекта в кабинет поставщика crm.bp-gr.ru, оставаясь зелёным - ответ кабинета
тест не проверяет. Поставлена заглушка.

ExternalServiceDownAlertTest избавлен от зависимости от сети - закрыт хвост,
тянувшийся с 30.07: падал в общем прогоне, проходил в одиночку.

InAppNotificationTest брал первую попавшуюся запись во всей таблице вместо
своей. SalesOverviewTest попадал в топ-4 клиентов по удаче: при запросе
всего отдела отбор не ограничен ничем, все тенанты базы с нулём лидов равны.
Клиенту даны настоящие лиды - место в четвёрке заслуженное.

Проверено: полный прогон 4063/4059 зелёный, статанализ 0, код-стиль моих
файлов чисто. На бой ничего не выкачено.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 13:29:10 +03:00

83 lines
4.1 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\External\RefreshExternalBalancesJob;
use App\Mail\ExternalServiceDownMail;
use App\Services\External\BalanceReading;
use App\Services\External\DadataBalanceProvider;
use App\Services\External\ExternalBalanceRefresher;
use App\Services\External\LivenessReading;
use App\Services\External\SupplierBalanceProvider;
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);
afterEach(fn () => ExternalBalanceRefresher::resetLivenessProbes());
function stubBalanceProvidersOk(): void
{
// 🔴 Заглушка на ВСЕ внешние обращения. Пробы живости, которые тест не подменяет
// (yookassa / self_render / sales_finder), ходили в настоящую сеть: ответ живого
// мира решал, покраснеет тест или нет. Отсюда «падает в общем прогоне, проходит
// в одиночку» — хвост Х4, тянувшийся с 30.07.2026. Пустой ответ 200 = «сервис жив»,
// а красным в каждом тесте делается ровно одна нужная проба через useLivenessProbes().
Http::fake();
app()->instance(DadataBalanceProvider::class, fakeProvider('dadata', BalanceReading::ok('dadata', 9000, 'RUB', 100)));
app()->instance(SupplierBalanceProvider::class, fakeProvider('supplier', BalanceReading::ok('supplier', 90000, 'RUB', null)));
app()->instance(YandexCloudBalanceProvider::class, fakeProvider('yandex_cloud', BalanceReading::ok('yandex_cloud', 90000, 'RUB', 100)));
// captcha — единственная проба, которую эти тесты подменяют через useLivenessProbes();
// остальные пробы (yookassa/self_render/sales_finder) идут в реальные классы без Http::fake().
// self_render по умолчанию не настроен (grey, без сети). sales_finder же по умолчанию
// смотрит на боевой домен (config/services.php) — обнуляем его здесь, иначе тест бьёт
// по сети и красный ответ ломает ожидание «письмо не ушло».
config()->set('services.sales_finder.base_url', '');
}
it('шлёт одно письмо, когда сервис впервые покраснел', function () {
Mail::fake();
stubBalanceProvidersOk();
ExternalBalanceRefresher::useLivenessProbes([
fakeProbe('captcha', LivenessReading::down('captcha', 'HTTP 500')),
]);
(new RefreshExternalBalancesJob)->handle(app(ExternalBalanceRefresher::class));
Mail::assertSent(ExternalServiceDownMail::class, 1);
});
it('не шлёт письмо, если сервис уже был красным (без спама)', function () {
Mail::fake();
stubBalanceProvidersOk();
// Предзаливаем строку: captcha уже красный.
DB::connection('pgsql_supplier')->table('external_service_balances')->updateOrInsert(
['service_key' => 'captcha'],
['light' => 'red', 'ok' => true, 'currency' => 'RUB', 'checked_at' => now(), 'updated_at' => now()],
);
ExternalBalanceRefresher::useLivenessProbes([
fakeProbe('captcha', LivenessReading::down('captcha', 'HTTP 500')),
]);
(new RefreshExternalBalancesJob)->handle(app(ExternalBalanceRefresher::class));
Mail::assertNothingSent();
});
it('не шлёт письмо, когда всё зелёное', function () {
Mail::fake();
stubBalanceProvidersOk();
ExternalBalanceRefresher::useLivenessProbes([
fakeProbe('captcha', LivenessReading::alive('captcha', 'ок')),
]);
(new RefreshExternalBalancesJob)->handle(app(ExternalBalanceRefresher::class));
Mail::assertNothingSent();
});