*/ public array $calls = []; public function stage(int $stage, int $total, string $label): void { $this->calls[] = [$stage, $total, $label]; } }; } function unusedFetcher(): Fetcher { return new class implements Fetcher { public function site(string $url): FetchedSite { throw new RuntimeException('в deep-пути не используется'); } public function directory(string $url): array { return []; } }; } /** Заглушка глубокого собирателя — ничего не находит, но даёт агенту пройти цикл по элементам. */ function emptyDeepCollector(): SourceCollector { return new class implements SourceCollector { public function collect(Fingerprint $fp): array { return []; } }; } it('изучение отмечает этап на КАЖДЫЙ элемент конкурента (холдинг)', function () { config(['autopodbor.deep_study' => true]); $spy = studySpyChannel(); $agent = new RealCompetitorAgent( unusedFetcher(), new FakeCompetitorAgent, deep: emptyDeepCollector(), progress: $spy, ); $agent->studyCompetitor(new StudyCompetitorRequest( competitor: [ 'name' => 'Холдинг', 'elements' => [ ['name' => 'Первый', 'sites' => ['a.ru'], 'cards' => []], ['name' => 'Второй', 'sites' => [], 'cards' => []], ['name' => 'Третий', 'sites' => [], 'cards' => []], ], ], regionCode: 16, )); expect($spy->calls)->toBe([ [1, 3, 'Изучаю: Первый'], [2, 3, 'Изучаю: Второй'], [3, 3, 'Изучаю: Третий'], ]); }); it('изучение конкурента без элементов — один этап «Изучаю фирму»', function () { config(['autopodbor.deep_study' => true]); $spy = studySpyChannel(); $agent = new RealCompetitorAgent( unusedFetcher(), new FakeCompetitorAgent, deep: emptyDeepCollector(), progress: $spy, ); $agent->studyCompetitor(new StudyCompetitorRequest( competitor: ['name' => 'Одна Фирма', 'site_url' => 'x.ru', 'elements' => []], regionCode: 16, )); expect($spy->calls)->toBe([[1, 1, 'Изучаю фирму']]); });