d9f22d1353
Полный набор стал зелёным целиком: 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>
83 lines
4.1 KiB
PHP
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();
|
|
});
|