stub('79990000077', qc: 0, region: 'Москва', provider: 'МТС'); * app()->instance(DaDataPhoneClient::class, $fake); * * Task 1 — Phase 1 Portal Client Imitation Harness. * Spec: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md */ class FakeDaDataPhoneClient extends DaDataPhoneClient { /** * @var array phone => response (null = throw DaDataException) */ private array $stubs = []; /** * Переопределяем конструктор без вызова parent, чтобы не требовать HttpFactory в тестах. */ public function __construct() {} /** * Зарегистрировать детерминированный ответ для номера телефона. * * @param int $qc Код качества DaData (0=хорошо, 1=не уточнён, 2=мусор, 3=изменён, 7=иностранец) */ public function stub( string $phone, int $qc, ?string $region = null, ?string $provider = null, ): self { $this->stubs[$phone] = new DaDataPhoneResponse( qc: $qc, qcConflict: null, type: null, phone: $phone, provider: $provider, region: $region, city: null, timezone: null, raw: [ 'qc' => $qc, 'provider' => $provider, 'region' => $region, 'phone' => $phone, ], ); return $this; } /** * Зарегистрировать выброс DaDataException для номера телефона. * Используется для тестирования ветки деградации (Россвязь-fallback). */ public function stubThrows(string $phone): self { $this->stubs[$phone] = null; // null = throw return $this; } /** * Возвращает заранее зарегистрированный ответ или бросает DaDataException. * * @throws DaDataException Если стаб не зарегистрирован или зарегистрирован как throw. */ public function cleanPhone(string $phone): DaDataPhoneResponse { if (! array_key_exists($phone, $this->stubs)) { throw new DaDataException("FakeDaDataPhoneClient: no stub registered for phone {$phone}"); } $response = $this->stubs[$phone]; if ($response === null) { throw new DaDataException("FakeDaDataPhoneClient: stubbed to throw for phone {$phone}"); } return $response; } }