Files
portal/app/tests/Feature/Autopodbor/AutopodborQueueThrottleTest.php
T
Дмитрий f1ab608444 feat(автоподбор): масштаб и чистота шага 1 — пул EXA, очередь-throttle, батч агрегатора, узкие рубрики, дедуп по словарю
Масштаб (лимиты внешних сервисов — на КЛЮЧ, общий на всех клиентов):
- EXA: параллельный пул findSites (concurrency=5) вместо 40 запросов по одному.
- Агрегатор: батч вместо ~90 запросов по одному + чанкинг по 40 и длинный
  таймаут 90с (большой список gpt-4o-mini не успевал в 30с).
- Очередь autopodbor + WithoutOverlapping на всех 3 джобах — глобальный
  потолок = число воркеров; assertNoInFlight держит один подбор на клиента.

Чистота выдачи (универсально, без зашитых ниш):
- Анализатор: узкие рубрики, запрет зонтичных слов («финансовые услуги» тащила
  юрфирмы/банки).
- Дедуп: ключ имени минус слова из СЛОВАРЯ рубрик прогона («Яричъ Ломбард»→«Яричъ»);
  слово-категория из данных, не из списка. Гард: склейка только если остаётся
  ровно один слово-токен (генерики вроде «займы под залог» не трогаем).

TDD, 233/233 (unit+feature) зелёные. НЕ прод.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:18:09 +03:00

41 lines
1.8 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\Autopodbor\RunAutopodborResolveJob;
use App\Jobs\Autopodbor\RunAutopodborSearchJob;
use App\Jobs\Autopodbor\RunAutopodborStudyJob;
use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Support\Facades\Queue;
// Масштаб: лимиты внешних сервисов — на КЛЮЧ (общий на всех клиентов). Джобы автоподбора идут на
// ВЫДЕЛЕННУЮ очередь autopodbor — число её воркеров = глобальный потолок одновременных подборов.
// Плюс защита от нахлёста (WithoutOverlapping) на случай ретрая/повторного dispatch.
it('поиск уходит на выделенную очередь autopodbor', function () {
Queue::fake();
RunAutopodborSearchJob::dispatch(123);
Queue::assertPushedOn('autopodbor', RunAutopodborSearchJob::class);
});
it('поиск защищён от нахлёста — middleware WithoutOverlapping по runId', function () {
$mw = (new RunAutopodborSearchJob(123))->middleware();
expect($mw)->toHaveCount(1)
->and($mw[0])->toBeInstanceOf(WithoutOverlapping::class);
});
it('шаг 2 (изучение) и резолв — на той же очереди и защищены от нахлёста', function () {
Queue::fake();
RunAutopodborStudyJob::dispatch(7);
RunAutopodborResolveJob::dispatch(8);
Queue::assertPushedOn('autopodbor', RunAutopodborStudyJob::class);
Queue::assertPushedOn('autopodbor', RunAutopodborResolveJob::class);
expect((new RunAutopodborStudyJob(7))->middleware()[0])->toBeInstanceOf(WithoutOverlapping::class)
->and((new RunAutopodborResolveJob(8))->middleware()[0])->toBeInstanceOf(WithoutOverlapping::class);
});