32df332901
Приёмка глазами вскрыла: кабинет МТС требует «Заголовок объявления» (до 40 знаков), когда в объявлении ссылка на САЙТ, а не на телеграм-канал. Робот про это поле не знал, «Продолжить» молча не срабатывало, кампания вставала на шаге «Объявление» — в бою уже ПОСЛЕ списания денег. Проверено живьём: 2234454 (сайт — встала) против 2234462 (канал — дошла до подтверждения) и 2234490 (сайт с заголовком — дошла). Портал спрашивает заголовок заранее, на создании черновика: обязателен только для не-телеграмной ссылки (App\Support\TelegramLink), колонка ad_headline varchar(40), поле на экране появляется по той же развилке. Робот заполняет его в кабинете. Три ловушки, добытые живыми прогонами (описаны в коде): - поле дорисовывается в ОТВЕТ на ссылку, с задержкой — надо ждать, а не спрашивать; - под описание подходит несколько элементов — нужен .first(); - серая надпись внутри поля НЕ placeholder, а нарисованная подпись: поиск по атрибуту давал ноль совпадений при видимом на снимке поле. Опознаём по видимой надписи. Тесты: робот 130/130, ClientTg 250/250, экран 23/23. Полный прогон бэкенда — те же 13 падающих классов до и после правки (ни одного в телеграм-части). В baseline статанализа добавлен известный ложный класс Pest для нового файла тестов. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
110 lines
4.8 KiB
PHP
110 lines
4.8 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Jobs\ClientTg\RunTelegramCampaignJob;
|
|
use App\Models\ClientTg\Campaign;
|
|
use App\Models\ClientTg\RobotJob;
|
|
use App\Models\Tenant;
|
|
use App\Models\User;
|
|
use App\Services\ClientTg\TelegramAudienceService;
|
|
use App\Services\ClientTg\TelegramRobotRunner;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Queue;
|
|
|
|
/**
|
|
* «Заголовок объявления» — находка приёмки глазами 31.07.2026.
|
|
*
|
|
* 🔴 Кабинет МТС показывает ОБЯЗАТЕЛЬНОЕ поле «Заголовок объявления» (до 40 знаков),
|
|
* когда рекламируется САЙТ, а не телеграм-канал. Робот про него не знал: «Продолжить»
|
|
* не срабатывало, кампания вставала на шаге «Объявление» с невнятной причиной, а в
|
|
* бою деньги к этому моменту уже списаны. Проверено живьём на кампаниях 2234454
|
|
* (ссылка на сайт — встала) и 2234462 (ссылка на канал — дошла до подтверждения).
|
|
*
|
|
* Поэтому портал обязан спросить заголовок ЗАРАНЕЕ — на создании черновика, до денег.
|
|
*/
|
|
uses(RefreshDatabase::class);
|
|
|
|
beforeEach(function () {
|
|
$this->tenant = Tenant::factory()->create();
|
|
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
|
|
$this->actingAs($this->user);
|
|
});
|
|
|
|
/** @return array<string, mixed> */
|
|
function tgHeadlinePayload(array $overrides = []): array
|
|
{
|
|
return array_merge([
|
|
'ad_text' => 'Приходите к нам',
|
|
'ad_link' => 'https://t.me/example_channel',
|
|
'audience_kind' => 'list',
|
|
'budget_cap_rub' => '1000.00',
|
|
'phones' => ['79990000001', '79990000002'],
|
|
], $overrides);
|
|
}
|
|
|
|
it('реклама САЙТА без заголовка — не создаём кампанию', function () {
|
|
$response = $this->postJson('/api/telegram/campaigns', tgHeadlinePayload([
|
|
'ad_link' => 'https://liderra.ru',
|
|
]));
|
|
|
|
$response->assertStatus(422)->assertJsonValidationErrors('ad_headline');
|
|
expect(Campaign::count())->toBe(0);
|
|
});
|
|
|
|
it('реклама САЙТА с заголовком — кампания создаётся, заголовок сохранён', function () {
|
|
$response = $this->postJson('/api/telegram/campaigns', tgHeadlinePayload([
|
|
'ad_link' => 'https://liderra.ru',
|
|
'ad_headline' => 'Лидерра — заявки для бизнеса',
|
|
]));
|
|
|
|
$response->assertCreated();
|
|
expect(Campaign::first()->ad_headline)->toBe('Лидерра — заявки для бизнеса');
|
|
});
|
|
|
|
it('реклама телеграм-канала — заголовок не нужен', function () {
|
|
$response = $this->postJson('/api/telegram/campaigns', tgHeadlinePayload());
|
|
|
|
$response->assertCreated();
|
|
expect(Campaign::first()->ad_headline)->toBeNull();
|
|
});
|
|
|
|
it('заголовок длиннее 40 знаков не принимается — кабинет режет молча', function () {
|
|
$response = $this->postJson('/api/telegram/campaigns', tgHeadlinePayload([
|
|
'ad_link' => 'https://liderra.ru',
|
|
'ad_headline' => str_repeat('я', 41),
|
|
]));
|
|
|
|
$response->assertStatus(422)->assertJsonValidationErrors('ad_headline');
|
|
});
|
|
|
|
it('ссылка на бота t.me тоже считается телеграмом — заголовок не требуем', function () {
|
|
$response = $this->postJson('/api/telegram/campaigns', tgHeadlinePayload([
|
|
'ad_link' => 'https://t.me/liderra_bot?start=1',
|
|
]));
|
|
|
|
$response->assertCreated();
|
|
});
|
|
|
|
it('заголовок доезжает до задания робота', function () {
|
|
config(['client_tg.robot.transport' => 'poll', 'client_tg.sandbox' => true]);
|
|
Queue::fake();
|
|
|
|
$this->postJson('/api/telegram/campaigns', tgHeadlinePayload([
|
|
'ad_link' => 'https://liderra.ru',
|
|
'ad_headline' => 'Лидерра',
|
|
]))->assertCreated();
|
|
|
|
$campaign = Campaign::first();
|
|
$this->postJson("/api/telegram/campaigns/{$campaign->id}/launch")->assertOk();
|
|
|
|
// Джоб в очереди подделан — выполняем его тело руками, как это делают
|
|
// соседние тесты канала: важно, что в задании робота есть заголовок.
|
|
(new RunTelegramCampaignJob($campaign->id, $this->tenant->id))->handle(
|
|
app(TelegramAudienceService::class),
|
|
app(TelegramRobotRunner::class),
|
|
);
|
|
|
|
expect(RobotJob::first()->payload['adHeadline'])->toBe('Лидерра');
|
|
});
|