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>
This commit is contained in:
@@ -35,3 +35,56 @@ it('surfaces an API error as RuntimeException', function () {
|
||||
$client = new YandexDirectClient('https://api-sandbox.direct.yandex.com', 'T');
|
||||
expect(fn () => $client->addCpmBannerCampaign('C', '2026-08-01', '2026-08-14', 72000000, 90000000, 3, 14))->toThrow(RuntimeException::class);
|
||||
});
|
||||
|
||||
/**
|
||||
* 🔴 Язык ответа спрашиваем явно. Без заголовка Яндекс отвечает ПО-АНГЛИЙСКИ, и эти слова
|
||||
* уезжают клиенту в переписку и письмом: «Rejected at moderation.», «Accepted at moderation.».
|
||||
*
|
||||
* Замер 01.08.2026, боевой ключ, два одинаковых запроса подряд по объявлению 17787641507:
|
||||
*
|
||||
* без заголовка → StatusClarification = "\nRejected at moderation."
|
||||
* Accept-Language: ru → StatusClarification = "\nОтклонено на модерации."
|
||||
*
|
||||
* 🪤 В `cabinet-flow.md` §7.7 записан русский ответ — и запись не врёт: тот замер делали
|
||||
* ДРУГИМ инструментом, который язык просил. Разбор отписок построили по показаниям прибора,
|
||||
* которым боевой код не пользуется. Мерить надо той же дорогой, по которой ходит продукт.
|
||||
*/
|
||||
it('asks Yandex to answer in Russian', function () {
|
||||
Http::fake(['*/json/v5/ads' => Http::response(['result' => ['Ads' => []]], 200)]);
|
||||
|
||||
(new YandexDirectClient('https://api-sandbox.direct.yandex.com', 'T'))->getAdsModeration([1]);
|
||||
|
||||
Http::assertSent(fn ($req) => $req->hasHeader('Accept-Language', 'ru'));
|
||||
});
|
||||
|
||||
/**
|
||||
* 🔴 Отказ включения приходит в «предупреждениях», а не в «ошибках» — и молча теряется.
|
||||
*
|
||||
* Замер 01.08.2026, боевой ключ, объявление 17787641513, ответ целиком:
|
||||
*
|
||||
* {"result":{"ResumeResults":[{"Id":17787641513,
|
||||
* "Warnings":[{"Code":10201,"Message":"Объявление не остановлено"}],"Errors":[]}]}}
|
||||
*
|
||||
* `Errors` пуст, значит проверка по ошибкам молчит и портал считает, что справился.
|
||||
* На бою это шло двое суток: обход каждые два часа, около двадцати попыток включить
|
||||
* 13 объявлений, ноль записей в журнале, показов ноль. Клиент при этом видит «Идут показы».
|
||||
*/
|
||||
it('reports ads Yandex refused to resume instead of failing silently', function () {
|
||||
Http::fake(['*/json/v5/ads' => Http::response(['result' => ['ResumeResults' => [
|
||||
['Id' => 111, 'Warnings' => [['Code' => 10201, 'Message' => 'Объявление не остановлено']], 'Errors' => []],
|
||||
['Id' => 222, 'Warnings' => [], 'Errors' => []],
|
||||
]]], 200)]);
|
||||
|
||||
$otkazy = (new YandexDirectClient('https://api-sandbox.direct.yandex.com', 'T'))->resumeAds([111, 222]);
|
||||
|
||||
expect($otkazy)->toBe([111 => 'Объявление не остановлено']);
|
||||
});
|
||||
|
||||
/** Всех включили — отказов нет, и это не путается с «мы не спросили». */
|
||||
it('returns no refusals when every ad resumed', function () {
|
||||
Http::fake(['*/json/v5/ads' => Http::response(['result' => ['ResumeResults' => [
|
||||
['Id' => 111, 'Warnings' => [], 'Errors' => []],
|
||||
]]], 200)]);
|
||||
|
||||
expect((new YandexDirectClient('https://api-sandbox.direct.yandex.com', 'T'))->resumeAds([111]))->toBe([]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user