Files
portal/app/tests/Unit/Advertising/ModerationReasonTest.php
T
Дмитрий b7bfe5a46f fix реклама Яндекса: три поломки модерации, вскрытые заходом в живой кабинет
Кампания 713175197 третьи сутки не показывалась при замороженных у клиента
деньгах, а программный интерфейс на всех уровнях рапортовал «Идут показы».
Правду отдала только подпись на экране кабинета: «Для показа в заданных
регионах предоставьте документы» — 13 объявлений придержаны, 2 отклонены.

1. Яндекс отвечает машине ПО-АНГЛИЙСКИ, если не попросить русский.
   Замер боевым ключом, два одинаковых запроса подряд: без заголовка
   «Rejected at moderation.», с Accept-Language ru «Отклонено на модерации.».
   Английская строка уезжала клиенту в переписку и письмом как пояснение
   модератора, честная заглушка «причину выясняем» не срабатывала никогда,
   а на следующем обходе английская строка ложилась ПОВЕРХ доклада разведчика
   — проверено по боевой ленте: 30.07 в 15:02 робот принёс полную причину,
   в 17:00 её накрыло. Лечение: спрашиваем язык явно плюс второй заслон —
   английские отписки узнаются как отписки.

2. Отказ включения Яндекс кладёт в Warnings, а код смотрел только в Errors.
   Ответ целиком: ResumeResults с Warnings 10201 «Объявление не остановлено»
   при пустом Errors. Исключения нет, портал считал, что справился: двое суток
   обход каждые два часа поднимал 13 объявлений, ноль записей в журнале.
   Лечение: resumeAds возвращает, кого включить не дали.

3. Новый исход «принято, но придержано» порталу не был известен вовсе.
   Вердикт ACCEPTED, ярлыка «Отклонено» нет, разведчик не ходил — клиент видел
   «Идут показы» при нулевых показах. Теперь по отказу включения клиенту идёт
   сообщение «Яндекс принял объявление, но пока не показывает его. Причину
   выясняем» — текст выбран владельцем — и туда же едет разведка.

Ловушка, обойденная по дороге: включение спрашиваем ДО разбора объявлений.
Наоборот — сообщение о придержке легло бы поверх доклада разведчика, и обход
начал бы чередовать их по кругу: защита от дублей смотрит на последнее сообщение.

Замеры. Восемь новых сторожей, все написаны ДО починки и падали именно
на живых данных. Отдельный сторож на противоположный случай — включённому
объявлению разведку не заводим — был зелёным с самого начала.
Портал 4083 теста, 4043 прошло, 16 упало: те же шесть давних классов и ровно
те же числа, что до работы, было 4069/4029/16. Плюс 14 тестов — ровно новые.
Pint и Larastan по изменённым файлам чистые.

Главный урок записан в cabinet-flow.md §7.9: в §7.7 лежит правдивый РУССКИЙ
ответ Яндекса, снятый ДРУГИМ инструментом, который язык просил. Разбор отписок
построили по показаниям прибора, которым продукт не пользуется. Мерить надо той
же дорогой, по которой ходит боевой код.

На боевой НЕ выкатывалось.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 15:12:40 +03:00

47 lines
2.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Services\Advertising\ModerationReason;
use Tests\TestCase;
uses(TestCase::class);
/**
* Отписки Яндекса на ОБОИХ языках. Русские были известны с 28.07.2026, английские
* намерены 01.08.2026 на бою: без заголовка языка Яндекс отвечает по-английски, и эта
* строка уезжала клиенту в переписку как «пояснение модератора».
*
* Второй заслон нужен даже после того, как язык стали спрашивать: заголовок могут
* перестать слушать, и тогда клиент снова получит машинную строку вместо честного
* «выясняем». Один замок на дверь, второй на калитку.
*/
it('treats a brush-off as no reason at all, in either language', function (string $otpiska) {
expect(ModerationReason::forRejected($otpiska))->toBe(ModerationReason::PRICHINA_NEIZVESTNA);
})->with([
'русская с точкой' => "\nОтклонено на модерации.",
'русская без точки' => 'Отклонено на модерации',
'русская про ожидание' => 'Ожидает модерации.',
'английская с точкой' => "\nRejected at moderation.",
'английская без точки' => 'Rejected at moderation',
'английская про ожидание' => 'Pending moderation.',
'пусто' => '',
]);
/**
* 🔴 Настоящую причину не трогаем ни на каком языке. Ровно ради неё весь разбор
* и затевался: её приносит робот-разведчик с экрана кабинета, и она обязана доехать
* до клиента слово в слово, со всеми абзацами.
*/
it('passes a real moderator explanation through untouched', function () {
$nastoyashchaya = "Нет предупреждения: финансовые услуги\n\n"
.'Объявление о финансовых услугах должно содержать наименование юридического лица.';
expect(ModerationReason::forRejected($nastoyashchaya))->toBe($nastoyashchaya);
});
/** Края обрезаем, внутренние переносы нет — модератор перечисляет претензии абзацами. */
it('trims the edges but keeps the paragraphs', function () {
expect(ModerationReason::normalize("\n первая\n\nвторая \n"))->toBe("первая\n\nвторая");
});