create(); $campaign = AdCampaign::create([ 'tenant_id' => $tenant->id, 'name' => 'C', 'audience_days' => 10, 'use_uploaded_list' => false, ]); return [$tenant, $campaign]; } it('текст сообщения хранится целиком, длиннее 255 знаков в том числе', function () { [$tenant, $campaign] = messageCampaign(); // Модератор перечисляет претензии списком — 255 знаков не хватает. Именно предел // колонки moderation_reason уже валил обход модерации целиком. $long = str_repeat('Причина отказа очень подробная. ', 40); $msg = AdCampaignMessage::create([ 'tenant_id' => $tenant->id, 'campaign_id' => $campaign->id, 'author' => AdCampaignMessage::AUTHOR_YANDEX, 'body' => $long, ]); expect(mb_strlen($msg->refresh()->body))->toBe(mb_strlen($long)); }); it('пояснение Яндекса ложится в ленту', function () { [$tenant, $campaign] = messageCampaign(); app(CampaignMessageService::class)->postFromYandex($campaign, null, 'Изображение не соответствует требованиям'); $messages = AdCampaignMessage::where('campaign_id', $campaign->id)->get(); expect($messages)->toHaveCount(1) ->and($messages->first()->author)->toBe(AdCampaignMessage::AUTHOR_YANDEX) ->and($messages->first()->body)->toBe('Изображение не соответствует требованиям'); }); /** * Опрос модерации бежит по расписанию. Пока Яндекс не передумал, он присылает ОДНУ И ТУ ЖЕ * причину при каждом обходе. Без защиты лента за сутки превратится в сотню одинаковых строк, * и клиенту прилетит сотня писем. */ it('тот же текст по тому же баннеру второй раз в ленту не попадает', function () { [$tenant, $campaign] = messageCampaign(); $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', ]); $service = app(CampaignMessageService::class); $service->postFromYandex($campaign, $banner->id, 'Одно и то же'); $second = $service->postFromYandex($campaign, $banner->id, 'Одно и то же'); expect($second)->toBeNull() ->and(AdCampaignMessage::where('campaign_id', $campaign->id)->count())->toBe(1); }); /** Другой размер блока — другая беда, её надо показать отдельно. */ it('тот же текст по ДРУГОМУ баннеру в ленту попадает', function () { [$tenant, $campaign] = messageCampaign(); $slot = [ 'tenant_id' => $tenant->id, 'campaign_id' => $campaign->id, 'bytes' => 1000, 'included' => true, ]; $a = AdCampaignBanner::create($slot + ['width' => 300, 'height' => 250, 'path' => 'ad-banners/a/300x250.jpg']); $b = AdCampaignBanner::create($slot + ['width' => 728, 'height' => 90, 'path' => 'ad-banners/a/728x90.jpg']); $service = app(CampaignMessageService::class); $service->postFromYandex($campaign, $a->id, 'Одно и то же'); $service->postFromYandex($campaign, $b->id, 'Одно и то же'); expect(AdCampaignMessage::where('campaign_id', $campaign->id)->count())->toBe(2); }); /** Яндекс передумал и написал другое — это новость, её показываем. */ it('изменившийся текст по тому же баннеру в ленту попадает', function () { [$tenant, $campaign] = messageCampaign(); $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', ]); $service = app(CampaignMessageService::class); $service->postFromYandex($campaign, $banner->id, 'Первая претензия'); $service->postFromYandex($campaign, $banner->id, 'Вторая претензия'); expect(AdCampaignMessage::where('campaign_id', $campaign->id)->count())->toBe(2); }); it('пустой текст в ленту не кладём', function () { [, $campaign] = messageCampaign(); expect(app(CampaignMessageService::class)->postFromYandex($campaign, null, ' '))->toBeNull() ->and(AdCampaignMessage::where('campaign_id', $campaign->id)->count())->toBe(0); }); /** * 🪤 Ловушка, которую открыла разведка. Дедуп сравнивал текст только с ПОСЛЕДНИМ сообщением * Яндекса. Порядок в жизни такой: обход модерации кладёт «причину выясняем» → робот приносит * настоящую причину → следующий обход снова кладёт «причину выясняем», потому что последним * теперь лежит доклад робота. Клиент, уже прочитавший настоящую причину, получал бы поверх * неё письмо «Яндекс отклонил рекламу, но причину не назвал». */ it('прежний текст Яндекса не всплывает в ленте второй раз поверх свежего', function () { [$tenant, $campaign] = messageCampaign(); $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', ]); $service = app(CampaignMessageService::class); $service->postFromYandex($campaign, $banner->id, 'Причину выясняем'); $service->postFromYandex($campaign, $banner->id, 'Нет предупреждения: финансовые услуги'); $again = $service->postFromYandex($campaign, $banner->id, 'Причину выясняем'); expect($again)->toBeNull() ->and(AdCampaignMessage::where('campaign_id', $campaign->id)->count())->toBe(2); });