Files
portal/app/tests/Feature/Advertising/CampaignModerationAndStopTest.php
T

178 lines
6.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Events\AdvertisingStopped;
use App\Jobs\SyncCampaignModerationJob;
use App\Models\AdCampaign;
use App\Models\AdCampaignAd;
use App\Models\Tenant;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Http;
use Tests\Concerns\SharesSupplierPdo;
// Джоб намеренно обходит running/pending-кампании ВСЕХ тенантов — без изоляции
// между кейсами кампания из одного теста осталась бы видна следующему.
uses(RefreshDatabase::class);
// Джоб перечисляет кампании через pgsql_supplier (BYPASSRLS) — без share PDO
// вставленные в тест-транзакции кампании (default pgsql-соединение) не видны
// второму соединению до commit'а.
uses(SharesSupplierPdo::class);
it('модерация: REJECTED объявление помечает кампанию rejected (passthrough)', function () {
config([
'services.yandex_direct.enabled' => true,
'services.yandex_direct.base_url' => 'https://api-sandbox.direct.yandex.com',
'services.yandex_direct.token' => 'DIRTOKEN',
]);
Http::fake([
'*/json/v5/ads' => Http::response(['result' => ['Ads' => [
['Id' => 555, 'Status' => 'REJECTED', 'State' => 'OFF', 'StatusClarification' => 'Нет гарантий'],
]]], 200),
]);
$tenant = Tenant::factory()->create();
$campaign = AdCampaign::create([
'tenant_id' => $tenant->id,
'name' => 'Кампания на модерации',
'status' => AdCampaign::STATUS_PENDING_MODERATION,
'yandex_campaign_id' => 222,
'weekly_budget_rub' => '500.00',
'audience_days' => 10,
'use_uploaded_list' => true,
]);
$ad = AdCampaignAd::create([
'tenant_id' => $tenant->id,
'campaign_id' => $campaign->id,
'title' => 'Заголовок',
'text' => 'Текст объявления',
'href' => 'https://example.test',
'yandex_ad_id' => 555,
'moderation_status' => 'MODERATION',
]);
app(SyncCampaignModerationJob::class)->handle();
$ad->refresh();
expect($ad->moderation_status)->toBe('REJECTED');
expect($ad->moderation_reason)->toBe('Нет гарантий');
$campaign->refresh();
expect($campaign->status)->toBe(AdCampaign::STATUS_REJECTED);
});
it('модерация: все ACCEPTED переводит кампанию в running (passthrough)', function () {
config([
'services.yandex_direct.enabled' => true,
'services.yandex_direct.base_url' => 'https://api-sandbox.direct.yandex.com',
'services.yandex_direct.token' => 'DIRTOKEN',
]);
Http::fake([
'*/json/v5/ads' => Http::response(['result' => ['Ads' => [
['Id' => 555, 'Status' => 'ACCEPTED', 'State' => 'ON', 'StatusClarification' => null],
]]], 200),
]);
$tenant = Tenant::factory()->create();
$campaign = AdCampaign::create([
'tenant_id' => $tenant->id,
'name' => 'Кампания на модерации 2',
'status' => AdCampaign::STATUS_PENDING_MODERATION,
'yandex_campaign_id' => 223,
'weekly_budget_rub' => '500.00',
'audience_days' => 10,
'use_uploaded_list' => true,
]);
$ad = AdCampaignAd::create([
'tenant_id' => $tenant->id,
'campaign_id' => $campaign->id,
'title' => 'Заголовок',
'text' => 'Текст объявления',
'href' => 'https://example.test',
'yandex_ad_id' => 555,
'moderation_status' => 'MODERATION',
]);
app(SyncCampaignModerationJob::class)->handle();
$ad->refresh();
expect($ad->moderation_status)->toBe('ACCEPTED');
$campaign->refresh();
expect($campaign->status)->toBe(AdCampaign::STATUS_RUNNING);
});
it('AdvertisingStopped ставит кампанию тенанта на паузу через реальный слушатель', function () {
config([
'services.yandex_direct.enabled' => true,
'services.yandex_direct.base_url' => 'https://api-sandbox.direct.yandex.com',
'services.yandex_direct.token' => 'DIRTOKEN',
]);
Http::fake([
'*/json/v5/campaigns' => Http::response(['result' => []], 200),
]);
$tenant = Tenant::factory()->create();
$campaign = AdCampaign::create([
'tenant_id' => $tenant->id,
'name' => 'Кампания в работе',
'status' => AdCampaign::STATUS_RUNNING,
'yandex_campaign_id' => 224,
'weekly_budget_rub' => '500.00',
'audience_days' => 10,
'use_uploaded_list' => true,
]);
// Без Event::fake — доказываем, что слушатель реально ЗАРЕГИСТРИРОВАН и сработал.
event(new AdvertisingStopped($tenant->id));
$campaign->refresh();
expect($campaign->status)->toBe(AdCampaign::STATUS_STOPPED_NO_FUNDS);
Http::assertSent(function ($request) {
return str_contains($request->url(), '/json/v5/campaigns')
&& ($request->data()['method'] ?? null) === 'suspend';
});
});
it('AdvertisingStopped не трогает кампанию чужого тенанта (явный tenant-фильтр)', function () {
config([
'services.yandex_direct.enabled' => true,
'services.yandex_direct.base_url' => 'https://api-sandbox.direct.yandex.com',
'services.yandex_direct.token' => 'DIRTOKEN',
]);
Http::fake([
'*/json/v5/campaigns' => Http::response(['result' => []], 200),
]);
$tenantA = Tenant::factory()->create();
$tenantB = Tenant::factory()->create();
$campaignA = AdCampaign::create([
'tenant_id' => $tenantA->id,
'name' => 'Кампания тенанта A',
'status' => AdCampaign::STATUS_RUNNING,
'yandex_campaign_id' => 225,
'weekly_budget_rub' => '500.00',
'audience_days' => 10,
'use_uploaded_list' => true,
]);
$campaignB = AdCampaign::create([
'tenant_id' => $tenantB->id,
'name' => 'Кампания тенанта B',
'status' => AdCampaign::STATUS_RUNNING,
'yandex_campaign_id' => 226,
'weekly_budget_rub' => '500.00',
'audience_days' => 10,
'use_uploaded_list' => true,
]);
event(new AdvertisingStopped($tenantA->id));
$campaignA->refresh();
$campaignB->refresh();
expect($campaignA->status)->toBe(AdCampaign::STATUS_STOPPED_NO_FUNDS);
expect($campaignB->status)->toBe(AdCampaign::STATUS_RUNNING);
});