From 83753fc0491a3bfeb17edd057a9852bcb372c94e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=BC=D0=B8=D1=82=D1=80=D0=B8=D0=B9?= Date: Tue, 28 Jul 2026 09:12:55 +0300 Subject: [PATCH] =?UTF-8?q?feat=20=D1=80=D0=B5=D0=BA=D0=BB=D0=B0=D0=BC?= =?UTF-8?q?=D0=B0=20=D0=B7=D0=B0=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D1=8B:?= =?UTF-8?q?=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=20=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D1=8B=20=D1=81=D0=BE=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B8=20=D0=B7=D0=B0=D1=89=D0=B8=D1=82=D0=B0=20=D0=BE?= =?UTF-8?q?=D1=82=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=BD=D0=BE=D0=B9=20=D0=B8=20=D1=82=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B6=D0=B5=20=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Advertising/CampaignMessageService.php | 60 +++++++++++++++ .../CampaignMessageServiceTest.php | 74 +++++++++++++++++++ 2 files changed, 134 insertions(+) create mode 100644 app/app/Services/Advertising/CampaignMessageService.php diff --git a/app/app/Services/Advertising/CampaignMessageService.php b/app/app/Services/Advertising/CampaignMessageService.php new file mode 100644 index 00000000..201bc1cc --- /dev/null +++ b/app/app/Services/Advertising/CampaignMessageService.php @@ -0,0 +1,60 @@ +getConnectionName(); + + $last = AdCampaignMessage::on($connection) + ->where('tenant_id', $campaign->tenant_id) + ->where('campaign_id', $campaign->id) + ->where('author', AdCampaignMessage::AUTHOR_YANDEX) + ->where(fn ($q) => $bannerId === null ? $q->whereNull('banner_id') : $q->where('banner_id', $bannerId)) + ->orderByDesc('id') + ->first(); + + if ($last !== null && $last->body === $body) { + return null; + } + + $message = new AdCampaignMessage([ + 'tenant_id' => (int) $campaign->tenant_id, + 'campaign_id' => (int) $campaign->id, + 'banner_id' => $bannerId, + 'author' => AdCampaignMessage::AUTHOR_YANDEX, + 'body' => $body, + ]); + $message->setConnection($connection); + $message->save(); + + return $message; + } +} diff --git a/app/tests/Feature/Advertising/CampaignMessageServiceTest.php b/app/tests/Feature/Advertising/CampaignMessageServiceTest.php index caf0cd83..a53bebdb 100644 --- a/app/tests/Feature/Advertising/CampaignMessageServiceTest.php +++ b/app/tests/Feature/Advertising/CampaignMessageServiceTest.php @@ -3,8 +3,10 @@ declare(strict_types=1); use App\Models\AdCampaign; +use App\Models\AdCampaignBanner; use App\Models\AdCampaignMessage; use App\Models\Tenant; +use App\Services\Advertising\CampaignMessageService; function messageCampaign(): array { @@ -32,3 +34,75 @@ it('текст сообщения хранится целиком, длинне 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); +});