2026-07-25 00:12:40 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use Illuminate\Console\Scheduling\Schedule;
|
|
|
|
|
|
|
|
|
|
it('registers advertising Direct jobs in the scheduler', function () {
|
|
|
|
|
$events = app(Schedule::class)->events();
|
|
|
|
|
$summaries = array_map(fn ($e) => $e->getSummaryForDisplay(), $events);
|
|
|
|
|
$joined = implode("\n", $summaries);
|
|
|
|
|
|
|
|
|
|
expect($joined)->toContain('ChargeCampaignSpendJob')
|
|
|
|
|
->and($joined)->toContain('SyncCampaignAudienceJob')
|
|
|
|
|
->and($joined)->toContain('SyncCampaignModerationJob');
|
|
|
|
|
});
|
2026-07-27 20:42:23 +03:00
|
|
|
|
2026-08-04 14:20:41 +03:00
|
|
|
/**
|
|
|
|
|
* Как часто портал спрашивает Яндекс о судьбе объявлений.
|
|
|
|
|
*
|
|
|
|
|
* 🔴 Было «раз в два часа», и клиент столько же времени видел на карточке «На модерации»,
|
|
|
|
|
* когда Яндекс уже всё одобрил и объявления шли. Поймано владельцем 04.08.2026 на живой
|
|
|
|
|
* кампании #11: проверка кончилась, а портал об этом узнал бы только через полтора часа.
|
|
|
|
|
* Обход дешёвый — чтение по кампаниям, ждущим вердикта, ничего в кабинете не меняет.
|
|
|
|
|
*/
|
2026-08-04 17:59:20 +03:00
|
|
|
/**
|
|
|
|
|
* Как часто портал узнаёт, сколько показов уже сделано.
|
|
|
|
|
*
|
|
|
|
|
* 🔴 Было «раз в сутки в 04:20», и это единственное место, где обновляется число
|
|
|
|
|
* показов на карточке. Кампанию #11 запустили в 04:39 — на двадцать минут позже
|
|
|
|
|
* обхода, и клиент до следующего утра видел «Показано 0», когда показы уже шли.
|
|
|
|
|
* Поймано владельцем 04.08.2026: «показано 0, почему? там один же есть».
|
|
|
|
|
*
|
|
|
|
|
* Списывать чаще безопасно по устройству: счётчик идемпотентен по ключу
|
|
|
|
|
* «yandex-imp:{кампания}:{показы}» и монотонен — списывается только прирост.
|
|
|
|
|
*/
|
|
|
|
|
it('узнаёт число показов каждый час, а не раз в сутки', function () {
|
|
|
|
|
$event = collect(app(Schedule::class)->events())
|
|
|
|
|
->first(fn ($e) => str_contains($e->getSummaryForDisplay(), 'ChargeCampaignSpendJob'));
|
|
|
|
|
|
|
|
|
|
expect($event)->not->toBeNull()
|
|
|
|
|
->and($event->expression)->toBe('0 * * * *');
|
|
|
|
|
});
|
|
|
|
|
|
2026-08-04 14:20:41 +03:00
|
|
|
it('спрашивает Яндекс о модерации каждые 15 минут', function () {
|
|
|
|
|
$event = collect(app(Schedule::class)->events())
|
|
|
|
|
->first(fn ($e) => str_contains($e->getSummaryForDisplay(), 'SyncCampaignModerationJob'));
|
|
|
|
|
|
|
|
|
|
expect($event)->not->toBeNull()
|
|
|
|
|
->and($event->expression)->toBe('*/15 * * * *');
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-27 20:42:23 +03:00
|
|
|
/**
|
|
|
|
|
* Сторож очереди робота обязан быть в расписании.
|
|
|
|
|
*
|
|
|
|
|
* Сама команда без расписания бесполезна: зависшее «в работе» задание держит очередь для
|
|
|
|
|
* ВСЕХ клиентов, а разбирать пробку руками некому — робот работает по ночам без людей.
|
|
|
|
|
*/
|
|
|
|
|
it('registers the creative job reaper in the scheduler', function () {
|
|
|
|
|
$events = app(Schedule::class)->events();
|
|
|
|
|
$joined = implode("\n", array_map(fn ($e) => $e->getSummaryForDisplay(), $events));
|
|
|
|
|
|
|
|
|
|
expect($joined)->toContain('creative-jobs:reap');
|
|
|
|
|
});
|