findSite('CarMoney', 'Красноярский край'))->toBeNull(); }); it('берёт домен первого результата (голый, без www/пути)', function () { Config::set('services.exa', ['key' => 'k', 'base_url' => 'https://api.exa.ai', 'timeout_sec' => 30]); $http = app(HttpFactory::class); $http->fake([ 'api.exa.ai/*' => $http->response([ 'results' => [['url' => 'https://www.carmoney.ru/about?utm=1'], ['url' => 'https://carmoney.ru']], ], 200), ]); $f = new ExaSiteFinder($http); expect($f->findSite('CarMoney', 'Красноярский край'))->toBe('carmoney.ru'); }); it('пропускает агрегаторы/каталоги и иностранные TLD, берёт первый «настоящий»', function () { Config::set('services.exa.key', 'k'); $http = app(HttpFactory::class); $http->fake([ 'api.exa.ai/*' => $http->response([ 'results' => [ ['url' => 'https://www.avito.ru/krasnoyarsk/zaim'], ['url' => 'https://banki.ru/products/cashmotor'], ['url' => 'https://cashmotor.kg/'], // иностранный TLD ['url' => 'https://cashmotor.ru/'], // настоящий ], ], 200), ]); $f = new ExaSiteFinder($http); expect($f->findSite('Cashmotor', 'Красноярский край'))->toBe('cashmotor.ru'); }); it('нет подходящих результатов или ошибка сети → null', function () { Config::set('services.exa.key', 'k'); $http = app(HttpFactory::class); $http->fake([ 'api.exa.ai/*' => $http->response(['results' => [['url' => 'https://zoon.ru/x']]], 200), ]); expect((new ExaSiteFinder($http))->findSite('X', 'Красноярский край'))->toBeNull(); $http2 = app(HttpFactory::class); $http2->fake(['api.exa.ai/*' => $http2->response('boom', 500)]); expect((new ExaSiteFinder($http2))->findSite('Y', 'Красноярский край'))->toBeNull(); }); it('findSites: пул отдаёт домены по именам; чёрный список → null', function () { Config::set('services.exa', ['key' => 'k', 'base_url' => 'https://api.exa.ai', 'timeout_sec' => 30, 'concurrency' => 5]); $http = app(HttpFactory::class); $http->fake(function ($request) use ($http) { $q = (string) ($request->data()['query'] ?? ''); if (str_contains($q, 'CarMoney')) { return $http->response(['results' => [['url' => 'https://www.carmoney.ru/about']]], 200); } if (str_contains($q, 'Пусто')) { return $http->response(['results' => [['url' => 'https://zoon.ru/x']]], 200); // только каталог → null } if (str_contains($q, 'Cashmotor')) { return $http->response(['results' => [['url' => 'https://cashmotor.ru/']]], 200); } return $http->response(['results' => []], 200); }); $map = (new ExaSiteFinder($http))->findSites(['CarMoney', 'Пусто', 'Cashmotor'], 'Красноярский край'); expect($map)->toBe(['CarMoney' => 'carmoney.ru', 'Пусто' => null, 'Cashmotor' => 'cashmotor.ru']); }); it('findSites: больше порции (concurrency=2) — все имена обработаны по чанкам', function () { Config::set('services.exa', ['key' => 'k', 'base_url' => 'https://api.exa.ai', 'timeout_sec' => 30, 'concurrency' => 2]); $http = app(HttpFactory::class); $http->fake(function ($request) use ($http) { $q = (string) ($request->data()['query'] ?? ''); preg_match('/Ф(\d)/u', $q, $m); $i = $m[1] ?? '0'; return $http->response(['results' => [['url' => "https://firma{$i}.ru/"]]], 200); }); $map = (new ExaSiteFinder($http))->findSites(['Ф1', 'Ф2', 'Ф3', 'Ф4', 'Ф5'], 'Красноярский край'); expect($map)->toHaveCount(5) ->and($map['Ф1'])->toBe('firma1.ru') ->and($map['Ф5'])->toBe('firma5.ru'); }); it('findSites: без ключа → все null', function () { Config::set('services.exa.key', ''); $map = (new ExaSiteFinder(app(HttpFactory::class)))->findSites(['A', 'B'], 'Красноярский край'); expect($map)->toBe(['A' => null, 'B' => null]); });