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); });