53fb7b7760
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
137 lines
5.3 KiB
PHP
137 lines
5.3 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Tests\Support\Imitation;
|
|
|
|
use App\Jobs\RouteSupplierLeadJob;
|
|
use App\Models\SupplierLead;
|
|
|
|
/**
|
|
* Инъектор синтетических заявок для имитационного стенда (Phase 1).
|
|
*
|
|
* Создаёт SupplierLead в БД и синхронно прогоняет RouteSupplierLeadJob —
|
|
* без HTTP-слоя, минуя secret/IP/rate-limit SupplierWebhookController.
|
|
*
|
|
* raw_payload формируется с теми же ключами (vid/project/phone/time/tag),
|
|
* что контроллер кладёт из $request->validate(). platform парсится по тому же
|
|
* правилу: B[123]_ → B1/B2/B3, иначе DIRECT.
|
|
*
|
|
* Используется исключительно в тест-инфраструктуре (Tests-namespace).
|
|
*
|
|
* Task 2 — Phase 1 Portal Client Imitation Harness.
|
|
* Spec: docs/superpowers/specs/2026-06-03-portal-client-imitation-phase1-design.md
|
|
*/
|
|
final class LeadInjector
|
|
{
|
|
/**
|
|
* Инъектировать заявку с сигналом «сайт».
|
|
*
|
|
* @param string $domain Домен сигнала (например, 'vashinvestor.ru').
|
|
* Составляет identifier в project-поле: "{$platform}_{$domain}".
|
|
* @param string $phone Телефон в формате 7XXXXXXXXXX.
|
|
* @param string|null $tag Тег региона (например, 'Москва'); может быть null.
|
|
* @param string $platform Префикс платформы: 'B1', 'B2', 'B3' или иное (→ DIRECT).
|
|
* @param int|null $vid Внешний ID заявки поставщика. Если null — генерируется уникальный.
|
|
*/
|
|
public function site(
|
|
string $domain,
|
|
string $phone,
|
|
?string $tag = null,
|
|
string $platform = 'B1',
|
|
?int $vid = null,
|
|
): SupplierLead {
|
|
$project = "{$platform}_{$domain}";
|
|
|
|
return $this->inject($project, $phone, $tag, $platform, $vid);
|
|
}
|
|
|
|
/**
|
|
* Инъектировать заявку с сигналом «звонок».
|
|
*
|
|
* @param string $number Номер телефона для call-сигнала (7XXXXXXXXXX).
|
|
* Составляет identifier в project-поле: "{$platform}_{$number}".
|
|
* @param string $phone Телефон звонящего в формате 7XXXXXXXXXX.
|
|
* @param string|null $tag Тег региона; может быть null.
|
|
* @param string $platform Префикс платформы: 'B1', 'B2', 'B3' или иное (→ DIRECT).
|
|
* @param int|null $vid Внешний ID заявки поставщика. Если null — генерируется уникальный.
|
|
*/
|
|
public function call(
|
|
string $number,
|
|
string $phone,
|
|
?string $tag = null,
|
|
string $platform = 'B1',
|
|
?int $vid = null,
|
|
): SupplierLead {
|
|
$project = "{$platform}_{$number}";
|
|
|
|
return $this->inject($project, $phone, $tag, $platform, $vid);
|
|
}
|
|
|
|
/**
|
|
* Общий внутренний метод создания SupplierLead + синхронный dispatch Job.
|
|
*
|
|
* raw_payload содержит ровно те ключи, что SupplierWebhookController кладёт
|
|
* из $request->validate(): vid, project, phone, time, tag (null → не добавляем,
|
|
* чтобы не нарушить nullable-контракт RouteSupplierLeadJob).
|
|
*
|
|
* platform парсится по тому же правилу, что Controller::parsePlatform():
|
|
* /^(B[123])_/ → B1/B2/B3, иначе DIRECT.
|
|
*/
|
|
private function inject(
|
|
string $project,
|
|
string $phone,
|
|
?string $tag,
|
|
string $platform,
|
|
?int $vid,
|
|
): SupplierLead {
|
|
$resolvedVid = $vid ?? $this->generateVid();
|
|
$parsedPlatform = $this->parsePlatform($project);
|
|
|
|
$rawPayload = [
|
|
'vid' => $resolvedVid,
|
|
'project' => $project,
|
|
'phone' => $phone,
|
|
'time' => time(),
|
|
];
|
|
if ($tag !== null) {
|
|
$rawPayload['tag'] = $tag;
|
|
}
|
|
|
|
$lead = SupplierLead::create([
|
|
'platform' => $parsedPlatform,
|
|
'raw_payload' => $rawPayload,
|
|
'vid' => $resolvedVid,
|
|
'phone' => $phone,
|
|
'received_at' => now(),
|
|
'source' => 'webhook',
|
|
]);
|
|
|
|
RouteSupplierLeadJob::dispatchSync($lead->id);
|
|
|
|
return $lead->refresh();
|
|
}
|
|
|
|
/**
|
|
* Парсит platform из project-поля — идентично Controller::parsePlatform().
|
|
* B[123]_ → B1/B2/B3, иначе DIRECT.
|
|
*/
|
|
private function parsePlatform(string $project): string
|
|
{
|
|
if (preg_match('/^(B[123])_/', $project, $m) === 1) {
|
|
return $m[1];
|
|
}
|
|
|
|
return 'DIRECT';
|
|
}
|
|
|
|
/**
|
|
* Генерирует уникальный vid для использования, когда явный vid не передан.
|
|
* Диапазон 1_000_000_000..9_999_999_999 — вне реальных vid поставщика (< 10^9).
|
|
*/
|
|
private function generateVid(): int
|
|
{
|
|
return random_int(1_000_000_000, 9_999_999_999);
|
|
}
|
|
}
|