Files
portal/app/tests/Unit/Autopodbor/Study/YandexPhoneReverseExpanderTest.php
T
Дмитрий bdecd08dcb perf(autopodbor): мульти-ключ xfetch round-robin — параллельность ключи×concurrency
Сбор 2ГИС упирался в лимит xf4.ru ~3-4 запроса за раз на КЛЮЧ (выше 429).
XfetchClient теперь принимает список ключей и раскидывает запросы по кругу:
размер пачки = concurrency × число ключей, каждому URL — свой ключ. Живой залп
подтвердил: 9 параллельно (3 ключа × 3) без 429 → лимит per-key, ключи дают ×3.

Ключи — только в .env (XFETCH_API_KEYS через запятую), в код/гит не идут; пусто →
работает по-старому на одиночном XFETCH_API_KEY. Совместимость: конструктор
принимает и строку, и список. TDD: XfetchClientTest 11/11, автоподбор зелёный.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-05 15:28:19 +03:00

108 lines
5.2 KiB
PHP

<?php
use App\Services\Autopodbor\Agent\Study\Yandex\YandexCardReader;
use App\Services\Autopodbor\Agent\Study\Yandex\YandexPhoneSearch;
use App\Services\Autopodbor\Agent\Study\YandexPhoneReverseExpander;
function fakeSearch(array $map): YandexPhoneSearch
{
return new class($map) implements YandexPhoneSearch
{
public function __construct(private array $map) {}
public function candidates(string $yandexSlug, string $phoneDigits): array
{
return $this->map[$phoneDigits] ?? [];
}
};
}
function fakeCards(array $map): YandexCardReader
{
return new class($map) implements YandexCardReader
{
public function __construct(private array $map) {}
public function read(string $cardUrl): ?array
{
return $this->map[$cardUrl] ?? null;
}
};
}
it('оставляет только карточку, где искомый номер РЕАЛЬНО есть (отсекает похожие/рыхлые)', function () {
$search = fakeSearch(['73912179941' => [
['name' => 'СМ.Сити', 'card_url' => 'https://yandex.ru/maps/org/sm/1/'],
['name' => 'Похожее СпецСтрой', 'card_url' => 'https://yandex.ru/maps/org/spec/2/'],
['name' => 'СМ Сити Капитанская', 'card_url' => 'https://yandex.ru/maps/org/smk/3/'],
]]);
$cards = fakeCards([
'https://yandex.ru/maps/org/sm/1/' => ['phones' => ['73912179941'], 'site' => 'https://sm-city.ru'],
'https://yandex.ru/maps/org/spec/2/' => ['phones' => ['73912990000'], 'site' => 'https://specstroy.ru'],
'https://yandex.ru/maps/org/smk/3/' => ['phones' => ['73912749569'], 'site' => 'https://smk.ru'],
]);
$exp = new YandexPhoneReverseExpander($search, $cards);
$res = $exp->expand('', ['73912179941'], [], '62/krasnoyarsk');
$domains = array_column($res->sites, 'domain');
expect($domains)->toContain('sm-city.ru')
->and($domains)->not->toContain('specstroy.ru')
->and($domains)->not->toContain('smk.ru');
});
it('пустой yandexSlug → разворот невозможен, пусто', function () {
$exp = new YandexPhoneReverseExpander(fakeSearch([]), fakeCards([]));
$res = $exp->expand('', ['73912179941'], [], '');
expect($res->sites)->toBe([])->and($res->phones)->toBe([]);
});
it('порог общей линии — если подтверждённых карточек больше порога, номер общий, пропускаем', function () {
$cands = [];
$cardsMap = [];
foreach (range(1, 5) as $i) {
$u = "https://yandex.ru/maps/org/o$i/$i/";
$cands[] = ['name' => "O$i", 'card_url' => $u];
$cardsMap[$u] = ['phones' => ['78001234567'], 'site' => "https://o$i.ru"];
}
$exp = new YandexPhoneReverseExpander(fakeSearch(['78001234567' => $cands]), fakeCards($cardsMap), crowdedCap: 4);
$res = $exp->expand('', ['78001234567'], [], '62/krasnoyarsk');
expect($res->sites)->toBe([]);
});
it('уже известный домен фирмы не повторяет', function () {
$search = fakeSearch(['73912179941' => [['name' => 'СМ', 'card_url' => 'https://yandex.ru/maps/org/sm/1/']]]);
$cards = fakeCards(['https://yandex.ru/maps/org/sm/1/' => ['phones' => ['73912179941'], 'site' => 'https://sm-city.ru']]);
$exp = new YandexPhoneReverseExpander($search, $cards);
$res = $exp->expand('', ['73912179941'], ['sm-city.ru'], '62/krasnoyarsk');
expect(array_column($res->sites, 'domain'))->not->toContain('sm-city.ru');
});
it('цепочка при maxDepth>=2: телефон найденной карточки разворачивается дальше', function () {
$search = fakeSearch([
'73912179941' => [['name' => 'A', 'card_url' => 'https://yandex.ru/maps/org/a/1/']],
'73912000002' => [['name' => 'B', 'card_url' => 'https://yandex.ru/maps/org/b/2/']],
]);
$cards = fakeCards([
'https://yandex.ru/maps/org/a/1/' => ['phones' => ['73912179941', '73912000002'], 'site' => 'https://a.ru'],
'https://yandex.ru/maps/org/b/2/' => ['phones' => ['73912000002'], 'site' => 'https://b-proekt.ru'],
]);
$exp = new YandexPhoneReverseExpander($search, $cards, maxDepth: 2);
$res = $exp->expand('', ['73912179941'], [], '62/krasnoyarsk');
expect(array_column($res->sites, 'domain'))->toContain('b-proekt.ru');
});
it('по умолчанию (глубина 1) НЕ разворачивает по номеру найденной карточки — дрейф отсечён', function () {
$search = fakeSearch([
'73912179941' => [['name' => 'A', 'card_url' => 'https://yandex.ru/maps/org/a/1/']],
'73912000002' => [['name' => 'B', 'card_url' => 'https://yandex.ru/maps/org/b/2/']],
]);
$cards = fakeCards([
'https://yandex.ru/maps/org/a/1/' => ['phones' => ['73912179941', '73912000002'], 'site' => 'https://a.ru'],
'https://yandex.ru/maps/org/b/2/' => ['phones' => ['73912000002'], 'site' => 'https://b-proekt.ru'],
]);
$exp = new YandexPhoneReverseExpander($search, $cards);
$res = $exp->expand('', ['73912179941'], [], '62/krasnoyarsk');
expect(array_column($res->sites, 'domain'))->not->toContain('b-proekt.ru');
});