Files
portal/app/tests/Feature/Sales/VkAudienceSyncTest.php
T
Дмитрий 7ed180b76b fix(sales): выключатель рекламы гасит и ВК, а не только Яндекс
Субагент убрал из SyncVkAudienceJob проверку общего рубильника enabled,
потому что с ней не проходили тесты. Чинить надо было тесты: без этой
проверки владелец выключает рекламу, Яндекс встаёт, а ВК продолжает
тратить деньги.

Тест на рубильник написан не через Http::assertNothingSent() — при трёх
номерах джоб и без защиты не дошёл бы до обращения к ВК, и такой тест
зелёный в любом случае (первая редакция именно так и прошла с вырезанной
защитой). Наблюдаемая разница одна: с рубильником статус канала остаётся
нетронутым, без него уезжает в waiting_volume. Проверено вырезанием.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 20:21:26 +03:00

110 lines
4.5 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\SyncVkAudienceJob;
use App\Models\SalesAdAudienceFirm;
use App\Models\SalesAdAudiencePhone;
use App\Models\SalesAdAudienceState;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Http;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
beforeEach(function () {
// Реклама по умолчанию выключена (enabled=false в схеме). Каждый тест канала
// включает её явно — иначе джоб честно выходит на общем рубильнике и мы бы
// проверяли не то, что думаем.
SalesAdAudienceState::current()->update(['enabled' => true]);
});
it('когда реклама выключена рубильником, джоб вообще не работает', function () {
// 🔴 Выключатель «Реклама» обязан гасить ОБЕ площадки. Иначе владелец
// выключает рекламу, Яндекс встаёт, а ВК продолжает тратить деньги.
//
// 🪤 Проверять здесь Http::assertNothingSent() БЕСПОЛЕЗНО: при трёх номерах
// джоб и без рубильника не дошёл бы до обращения к ВК, и тест был бы зелёным
// в любом случае (проверено 19.07 — первая редакция теста проходила и с
// вырезанной защитой). Наблюдаемая разница одна: с рубильником джоб выходит
// сразу и статус канала остаётся нетронутым, без рубильника — он успевает
// проставить 'waiting_volume'.
SalesAdAudienceState::current()->update(['enabled' => false, 'vk_status' => 'no_access']);
config(['services.vk_ads.token' => 'test-token']);
vkFirmWithPhones(3);
SyncVkAudienceJob::dispatchSync();
expect(SalesAdAudienceState::current()->vk_status)->toBe('no_access');
});
/** Заводит фирму для ВК с нужным числом номеров. */
function vkFirmWithPhones(int $count): SalesAdAudienceFirm
{
$firm = SalesAdAudienceFirm::create([
'firm_name' => 'Тестовая стоматология',
'warmup_started_at' => now(),
'channels' => 'vk',
]);
for ($i = 1; $i <= $count; $i++) {
SalesAdAudiencePhone::create([
'phone' => '7999'.str_pad((string) $i, 7, '0', STR_PAD_LEFT),
'firm_id' => $firm->id,
'expires_at' => now()->addDays(30),
'state' => 'active',
]);
}
return $firm;
}
it('без ключей доступа не трогает ВК и пишет «нет доступа»', function () {
config(['services.vk_ads.token' => '']);
vkFirmWithPhones(3);
Http::fake();
SyncVkAudienceJob::dispatchSync();
Http::assertNothingSent();
expect(SalesAdAudienceState::current()->vk_status)->toBe('no_access');
});
it('пока номеров меньше двух тысяч, не делает ни одного обращения к ВК', function () {
// 🔴 Порог 2000 — требование самого ВК к загрузке списка программой.
// Обращаться раньше бессмысленно и опасно: ВК считает попытки.
config(['services.vk_ads.token' => 'test-token']);
vkFirmWithPhones(3);
Http::fake();
SyncVkAudienceJob::dispatchSync();
Http::assertNothingSent();
$state = SalesAdAudienceState::current();
expect($state->vk_status)->toBe('waiting_volume')
->and($state->vk_last_error)->toBeNull();
});
it('фирмы, помеченные только Яндексом, в счёт для ВК не идут', function () {
config(['services.vk_ads.token' => 'test-token']);
$yandexOnly = SalesAdAudienceFirm::create([
'firm_name' => 'Только Яндекс', 'warmup_started_at' => now(), 'channels' => 'yandex',
]);
SalesAdAudiencePhone::create([
'phone' => '79990000001', 'firm_id' => $yandexOnly->id,
'expires_at' => now()->addDays(30), 'state' => 'active',
]);
Http::fake();
SyncVkAudienceJob::dispatchSync();
Http::assertNothingSent();
expect(SalesAdAudienceState::current()->vk_status)->toBe('waiting_volume');
});