Files
portal/app/tests/Unit/Advertising/YandexDirectClientTest.php
T

91 lines
5.0 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Services\Advertising\YandexDirectClient;
use Illuminate\Support\Facades\Http;
use Tests\TestCase;
uses(TestCase::class);
it('adds a retargeting list referencing an audience segment', function () {
Http::fake([
'*/json/v5/retargetinglists' => Http::response(['result' => ['AddResults' => [['Id' => 777]]]], 200),
]);
$client = new YandexDirectClient('https://api-sandbox.direct.yandex.com', 'TESTTOKEN');
$id = $client->addRetargetingList('Лидерра кампания #5', 58034825);
expect($id)->toBe(777);
Http::assertSent(function ($req) {
$body = $req->data();
return str_ends_with($req->url(), '/json/v5/retargetinglists')
&& $req->hasHeader('Authorization', 'Bearer TESTTOKEN')
&& $body['method'] === 'add'
&& $body['params']['RetargetingLists'][0]['Type'] === 'AUDIENCE'
&& $body['params']['RetargetingLists'][0]['Rules'][0]['Operator'] === 'ALL'
// Сегмент Аудиторий указывается с приставкой «20» — см. addRetargetingList().
&& $body['params']['RetargetingLists'][0]['Rules'][0]['Arguments'][0]['ExternalId'] === 2058034825;
});
});
it('surfaces an API error as RuntimeException', function () {
Http::fake(['*/json/v5/campaigns' => Http::response(['error' => ['error_string' => 'Нет доступа к API', 'error_code' => 53]], 200)]);
$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([]);
});