0ba7890bf9
Задача 15. Программный интерфейс Яндекса причину отказа не отдаёт — на отклонённое объявление приходит «Отклонено на модерации.» и всё. Причина висит только на экране кабинета, и добыть её может лишь тот, у кого есть глаза. Как теперь работает: опрос модерации видит отказ и ставит роботу задание разведки по этому объявлению; робот открывает список объявлений, находит ячейку своего объявления, кликает по статусу, раскрывает строку причины, читает текст и снимает одно окно; доклад уезжает порталу формой вместе со снимком; портал кладёт его в ленту от имени Яндекса слово в слово, клиенту письмо и колокольчик. Робот не понял, что видит — задание сбойное, владельцу письмо, в ленту клиенту ничего не сочиняем. В админке появилась ручка «ждёт разбора». Четыре ловушки, пойманные по дороге и проверенные вырезанием: 1. Дедуп разведки нельзя вешать на кампанию. Отказ никуда не девается, а обход бежит по расписанию: после закрытия первой разведки поставилась бы вторая, и робот ходил бы в кабинет по кругу. Ключ — номер объявления, журнал схемы v9.16. 2. Рубильник держал не выдачу задания, а построение клиента Директа. Разведке слепок креативов не нужен, значит при выключенном рубильнике она получила бы задание, и робот пошёл бы в живой кабинет. 3. Дедуп ленты сравнивал только с последним сообщением Яндекса. После доклада робота обход снова клал бы «причину выясняем» поверх настоящей причины. 4. Постановка разведки шла без tenant-контекста — на бою она не сработала бы ВООБЩЕ и молча: поиск дубля давал бы ноль, запись падала бы на политике доступа, всё это в предупреждение журнала при зелёных тестах. Поймал rls-reviewer. Лечение — своя транзакция с выставлением клиента, рецепт ChargeCampaignSpendJob. Сторож поставлен на сам механизм: обычным тестом это не ловится, они ходят суперпользователем. Заодно: разведке больше не снимается слепок креативов — лишний поход в живой Яндекс внутри открытой транзакции. Портал 382/382, робот 75/75, мест снятия заморозки денег по-прежнему четыре. На боевой не выкатывалось, рубильник Директа выключен. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
84 lines
3.7 KiB
PHP
84 lines
3.7 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
use App\Models\AdCampaign;
|
|
use App\Models\AdCampaignBanner;
|
|
use App\Models\AdCreativeJob;
|
|
use App\Models\Tenant;
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|
|
|
uses(DatabaseTransactions::class);
|
|
|
|
/**
|
|
* Список «ждёт разбора»: кампании, где робот сходил в кабинет и НЕ ПОНЯЛ, что видит.
|
|
*
|
|
* Это не про обычные отказы — их клиент разбирает сам, по причине в переписке. Это про
|
|
* сбой цепочки: разметка кабинета поменялась, страница не открылась, объявления в списке
|
|
* не нашлось. Без такого списка цепочка встанет молча: клиент будет ждать ответа, которого
|
|
* не будет, и никто об этом не узнает.
|
|
*/
|
|
function stuckSetup(string $status, string $kind = AdCreativeJob::KIND_INSPECT): array
|
|
{
|
|
$tenant = Tenant::factory()->create(['organization_name' => 'Окна Москва']);
|
|
$campaign = AdCampaign::create([
|
|
'tenant_id' => $tenant->id,
|
|
'name' => 'Кампания',
|
|
'status' => AdCampaign::STATUS_REJECTED,
|
|
'audience_days' => 10,
|
|
'budget_rub' => '500.00',
|
|
]);
|
|
|
|
$banner = AdCampaignBanner::create([
|
|
'tenant_id' => $tenant->id, 'campaign_id' => $campaign->id,
|
|
'width' => 300, 'height' => 250, 'bytes' => 1000, 'included' => true,
|
|
'path' => "ad-banners/{$tenant->id}/{$campaign->id}/300x250.jpg",
|
|
'yandex_ad_id' => 17787055204,
|
|
]);
|
|
|
|
$job = AdCreativeJob::create([
|
|
'tenant_id' => $tenant->id, 'campaign_id' => $campaign->id,
|
|
'kind' => $kind, 'status' => $status,
|
|
'yandex_ad_id' => $banner->yandex_ad_id,
|
|
'failure_reason' => 'разметка не совпала: окна BannerStatusCell.Popup нет',
|
|
'finished_at' => now(),
|
|
]);
|
|
|
|
return [$tenant, $campaign, $job];
|
|
}
|
|
|
|
test('в списке ждёт разбора видно кампанию, где разведка сорвалась', function () {
|
|
[$tenant, $campaign] = stuckSetup(AdCreativeJob::STATUS_FAILED);
|
|
|
|
$r = $this->getJson('/api/admin/advertising/robot-stuck');
|
|
$r->assertStatus(200);
|
|
|
|
$row = collect($r->json('data'))->firstWhere('campaign_id', $campaign->id);
|
|
|
|
expect($row)->not->toBeNull()
|
|
->and($row['tenant_name'])->toBe('Окна Москва')
|
|
->and($row['failure_reason'])->toContain('разметка не совпала')
|
|
->and((int) $row['yandex_ad_id'])->toBe(17787055204);
|
|
});
|
|
|
|
/** Удачная разведка разбора не ждёт — она уже всё рассказала клиенту в переписке. */
|
|
test('удачная разведка в список ждёт разбора не попадает', function () {
|
|
[, $campaign] = stuckSetup(AdCreativeJob::STATUS_DONE);
|
|
|
|
$r = $this->getJson('/api/admin/advertising/robot-stuck');
|
|
|
|
expect(collect($r->json('data'))->firstWhere('campaign_id', $campaign->id))->toBeNull();
|
|
});
|
|
|
|
/**
|
|
* Сорванная ЗАЛИВКА картинок — тоже сбой робота, и владельцу её видеть надо: кампания
|
|
* стоит и не запускается. Список один на все виды заданий.
|
|
*/
|
|
test('сорванная заливка картинок в списке тоже видна', function () {
|
|
[, $campaign] = stuckSetup(AdCreativeJob::STATUS_FAILED, AdCreativeJob::KIND_UPLOAD);
|
|
|
|
$r = $this->getJson('/api/admin/advertising/robot-stuck');
|
|
|
|
expect(collect($r->json('data'))->firstWhere('campaign_id', $campaign->id))->not->toBeNull();
|
|
});
|