76c67e1fc5
Замер боевого лимита xf4.ru показал безопасный потолок 4 одновременных запроса — выше идут 429 Too Many Requests, плюс есть лимит за окно времени. Сбор до 25 карточек филиалов в XfetchDirectoryFetcher грузился по одной секунды на каждую — узкое место шага 2. Новый интерфейс BatchPageFetcher + XfetchClient::htmlBatch грузит страницы порциями по services.xfetch.concurrency дефолт 4, параллелит внутри порции через Http::pool, ретраит пустые и 429. XfetchDirectoryFetcher берёт карточки одной пачкой если загрузчик batch-совместимый, иначе по одной. Два справочника шага 2 оставлены последовательно намеренно — иначе 2x4=8 за лимитом. Число выносится в XFETCH_CONCURRENCY без деплоя. По TDD. Бэкенд автоподбора 258/258, Pint чисто. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
83 lines
3.5 KiB
PHP
83 lines
3.5 KiB
PHP
<?php
|
|
|
|
use App\Services\Autopodbor\Agent\Fetch\XfetchClient;
|
|
use Illuminate\Support\Facades\Http;
|
|
|
|
it('декодирует HTML из base64-ответа xfetch', function () {
|
|
Http::fake([
|
|
'xf4.ru/*' => Http::response([
|
|
'status_code' => 200,
|
|
'response_body_base64' => base64_encode('<html>привет</html>'),
|
|
]),
|
|
]);
|
|
$client = new XfetchClient('test-key');
|
|
expect($client->html('https://2gis.ru/x'))->toBe('<html>привет</html>');
|
|
});
|
|
|
|
it('без ключа не ходит в сеть и возвращает пусто', function () {
|
|
Http::fake();
|
|
$client = new XfetchClient(null);
|
|
expect($client->html('https://2gis.ru/x'))->toBe('');
|
|
Http::assertNothingSent();
|
|
});
|
|
|
|
it('повторяет запрос при флаки-пустом рендере 2ГИС и берёт удачный', function () {
|
|
Http::fake(['xf4.ru/*' => Http::sequence()
|
|
->push(['response_body_base64' => ''], 200) // 1-й рендер пустой (флак)
|
|
->push(['response_body_base64' => base64_encode('<a href="tel:+73912920000">x</a>')], 200), // 2-й удачный
|
|
]);
|
|
$client = new XfetchClient('k', retries: 3);
|
|
expect($client->html('https://2gis.ru/x'))->toContain('73912920000');
|
|
});
|
|
|
|
it('после исчерпания ретраев возвращает пусто', function () {
|
|
Http::fake(['xf4.ru/*' => Http::response(['response_body_base64' => ''], 200)]);
|
|
$client = new XfetchClient('k', retries: 2);
|
|
expect($client->html('https://2gis.ru/x'))->toBe('');
|
|
Http::assertSentCount(2);
|
|
});
|
|
|
|
it('шлёт url, api_key и render:true на endpoint xfetch', function () {
|
|
Http::fake(['xf4.ru/*' => Http::response(['response_body_base64' => base64_encode('ok')])]);
|
|
(new XfetchClient('k123'))->html('https://2gis.ru/y');
|
|
Http::assertSent(fn ($r) => $r->url() === 'https://xf4.ru/fetch'
|
|
&& $r['url'] === 'https://2gis.ru/y'
|
|
&& $r['api_key'] === 'k123'
|
|
&& $r['render'] === true);
|
|
});
|
|
|
|
it('htmlBatch грузит несколько страниц и возвращает карту url=>html', function () {
|
|
Http::fake(['xf4.ru/*' => fn ($request) => Http::response([
|
|
'response_body_base64' => base64_encode('HTML:'.$request['url']),
|
|
])]);
|
|
$client = new XfetchClient('k', concurrency: 2);
|
|
|
|
$urls = ['https://a.ru', 'https://b.ru', 'https://c.ru'];
|
|
$out = $client->htmlBatch($urls);
|
|
|
|
expect($out)->toHaveKeys($urls)
|
|
->and($out['https://a.ru'])->toBe('HTML:https://a.ru')
|
|
->and($out['https://b.ru'])->toBe('HTML:https://b.ru')
|
|
->and($out['https://c.ru'])->toBe('HTML:https://c.ru');
|
|
Http::assertSentCount(3);
|
|
});
|
|
|
|
it('htmlBatch ретраит пустой ответ в пачке и берёт удачный', function () {
|
|
Http::fake(['xf4.ru/*' => Http::sequence()
|
|
->push(['response_body_base64' => ''], 200) // 1-я попытка пустая (флак)
|
|
->push(['response_body_base64' => base64_encode('OK')], 200), // ретрай удачен
|
|
]);
|
|
$client = new XfetchClient('k', retries: 2, concurrency: 4);
|
|
|
|
$out = $client->htmlBatch(['https://a.ru']);
|
|
|
|
expect($out['https://a.ru'])->toBe('OK');
|
|
});
|
|
|
|
it('htmlBatch без ключа не ходит в сеть', function () {
|
|
Http::fake();
|
|
$out = (new XfetchClient(null))->htmlBatch(['https://a.ru', 'https://b.ru']);
|
|
expect($out)->toBe(['https://a.ru' => '', 'https://b.ru' => '']);
|
|
Http::assertNothingSent();
|
|
});
|