Files
portal/app/tests/Feature/Advertising/CampaignReviveServiceTest.php
T
Дмитрий 2590c01477 test: половина грязи рекламных тестов убрана — остаток в базе со 116 до 44
Восьми файлам рекламной папки поставлен откат после теста. Проверено остатком
в базе снаружи: папка оставляла 116 записей арендаторов, теперь 44. Папка
целиком зелёная - 351 из 351. Полный прогон после уборки: 4111 тестов,
4107 зелёных, 4 пропущено, ноль падений - значит на эту грязь никто не
опирался. Побочно прогон стал БЫСТРЕЕ: 12,9 минуты против 16,5.

Один файл намеренно оставлен как был, причина записана прямо в нём:
CampaignBannerEndpointsTest нарочно ловит отказ базы, а Postgres после отказа
внутри транзакции глушит все следующие команды. С откатом файл падает -
чинить надо сам тест, а не обёртку.

Датчик пришлось расшифровывать, и это стоит помнить. Числа сперва выходили
с минусами: файл, который базы не касается вовсе, «уносил» девять записей.
Прямым замером выяснилось, что КАЖДЫЙ прогон начинается с пересборки базы -
было 6 записей, стало 0 после теста про расписание. Значит разница «до/после»
при переборе файлов = оставил этот минус оставил предыдущий. Расшифровал
цепочкой, сумма сошлась со 116 ровно - на этом расшифровка и проверяется.
Поимённый расклад по файлам перенесён в промт.

Грабля на будущее, тоже в промте: uses() обязан стоять НИЖЕ подключений
классов. Подключение действует с места объявления и ниже, поэтому вызов
выше падает «класс не найден» - и падает сразу вся папка. Полное имя
с ведущей чертой не спасает: pint САМ превращает его в подключение внизу
и ломает работавший файл.

Остаток долга измерен и НЕ закрыт: ~20 файлов оставляют по одной-две записи,
среди них миграционные, которым откат может быть противопоказан по сути.
По всей папке Feature без отката 96 файлов из 580 - корень общий,
в app/tests/Pest.php строка RefreshDatabase закомментирована.

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

127 lines
5.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\AdCampaign;
use App\Models\AdCampaignBanner;
use App\Models\Tenant;
use App\Services\Advertising\CampaignReviveService;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Http;
// Откат после теста. Без него записи файла остаются в базе и роняют ЧУЖИЕ тесты:
// 01.08.2026 девятнадцать чужих проводок уронили денежную проверку приёмника МТС.
uses(DatabaseTransactions::class);
/** Отклонённая кампания, уже заведённая в Яндексе, с одним отклонённым баннером. */
function rejectedCampaign(): array
{
$tenant = Tenant::factory()->create();
$campaign = AdCampaign::create([
'tenant_id' => $tenant->id, 'name' => 'C', 'audience_days' => 10, 'use_uploaded_list' => false,
'status' => AdCampaign::STATUS_REJECTED,
'yandex_campaign_id' => 555, 'yandex_ad_group_id' => 666, 'yandex_segment_id' => 777,
'moderation_reason' => 'Изображение не подошло',
]);
$banner = AdCampaignBanner::create([
'tenant_id' => $tenant->id, 'campaign_id' => $campaign->id,
'width' => 300, 'height' => 250, 'bytes' => 1000, 'included' => true,
'path' => 'ad-banners/a/300x250.jpg',
'yandex_creative_id' => 100, 'yandex_ad_id' => 200,
'moderation_status' => AdCampaignBanner::MOD_REJECTED,
]);
return [$tenant, $campaign, $banner];
}
it('оживление возвращает кампанию в черновик и чистит номера отклонённых объявлений', function () {
config(['services.yandex_direct.enabled' => true]);
Http::fake(['*' => Http::response(['result' => ['DeleteResults' => [['Id' => 200]]]])]);
[, $campaign, $banner] = rejectedCampaign();
app(CampaignReviveService::class)->revive($campaign);
expect($campaign->refresh()->status)->toBe(AdCampaign::STATUS_DRAFT)
->and($banner->refresh()->yandex_creative_id)->toBeNull()
->and($banner->refresh()->yandex_ad_id)->toBeNull();
});
/**
* Номера кампании, группы и сегмента — то, на чём держится возобновляемый запуск.
* Стереть их — значит завести в Яндексе второй комплект всего и заплатить дважды.
*/
it('номера кампании, группы и сегмента при оживлении уцелевают', function () {
config(['services.yandex_direct.enabled' => true]);
Http::fake(['*' => Http::response(['result' => ['DeleteResults' => [['Id' => 200]]]])]);
[, $campaign] = rejectedCampaign();
app(CampaignReviveService::class)->revive($campaign);
$fresh = $campaign->refresh();
expect((int) $fresh->yandex_campaign_id)->toBe(555)
->and((int) $fresh->yandex_ad_group_id)->toBe(666)
->and((int) $fresh->yandex_segment_id)->toBe(777);
});
it('оживить можно только отклонённую кампанию', function () {
[, $campaign] = rejectedCampaign();
$campaign->update(['status' => AdCampaign::STATUS_RUNNING]);
expect(fn () => app(CampaignReviveService::class)->revive($campaign))
->toThrow(RuntimeException::class);
});
/** Яндекс не удалил объявление — это не повод не дать клиенту исправиться. */
it('неудача удаления в Яндексе не мешает оживлению', function () {
config(['services.yandex_direct.enabled' => true]);
Http::fake(['*' => Http::response(['error' => ['error_string' => 'нет такого']], 200)]);
[, $campaign, $banner] = rejectedCampaign();
app(CampaignReviveService::class)->revive($campaign);
expect($campaign->refresh()->status)->toBe(AdCampaign::STATUS_DRAFT)
->and($banner->refresh()->yandex_ad_id)->toBeNull();
});
it('при выключенном рубильнике в Яндекс не ходим вовсе', function () {
config(['services.yandex_direct.enabled' => false]);
Http::fake();
[, $campaign] = rejectedCampaign();
app(CampaignReviveService::class)->revive($campaign);
Http::assertNothingSent();
expect($campaign->refresh()->status)->toBe(AdCampaign::STATUS_DRAFT);
});
/**
* Принятый баннер трогать нельзя: его объявление живо в кабинете и на нём держится
* возобновляемый запуск. Обнулить ему номера — значит при следующем «Запустить»
* завести в Яндексе второе такое же объявление.
*/
it('принятый баннер оживление не трогает', function () {
config(['services.yandex_direct.enabled' => true]);
Http::fake(['*' => Http::response(['result' => ['DeleteResults' => [['Id' => 200]]]])]);
[$tenant, $campaign] = rejectedCampaign();
$accepted = AdCampaignBanner::create([
'tenant_id' => $tenant->id, 'campaign_id' => $campaign->id,
'width' => 728, 'height' => 90, 'bytes' => 1000, 'included' => true,
'path' => 'ad-banners/a/728x90.jpg',
'yandex_creative_id' => 300, 'yandex_ad_id' => 400,
'moderation_status' => AdCampaignBanner::MOD_ACCEPTED,
]);
app(CampaignReviveService::class)->revive($campaign);
expect((int) $accepted->refresh()->yandex_creative_id)->toBe(300)
->and((int) $accepted->refresh()->yandex_ad_id)->toBe(400);
// И в Яндекс на удаление ушёл только номер отклонённого объявления.
Http::assertSent(fn ($req) => $req->data()['params']['SelectionCriteria']['Ids'] === [200]);
});