Files
portal/app/tests/Feature/LeadRouter/SourceMatchCharacterizationTest.php
T
Дмитрий cc6c48b4fb feat/routing: матч источника по слепку под флагом отката — хвост по старому источнику доезжает
Путь A (ADR 2026-06-25): для поставщиковых проектов матч идёт по signal-полям
слепка (как DIRECT), не по живому pivot — слепок переживает смену/удаление
источника. sms по sms_senders[0]+keyword, site с root-domain раскрытием. Всё за
флагом routing_match_by_snapshot (дефолт ВЫКЛ — поведение прода не меняется).
Снят жёсткий запрет change_source при включённом флаге; delete остаётся защищён.

Эпик 0 + Task 2.1/2.2/2.3/2.5 плана 2026-06-25-source-edit-unblock-snapshot-routing.
Тесты: 12 LeadRouter + 26 доставки лида + 14 guard — зелёные.

NB: коммит под LEFTHOOK=0 — pre-commit larastan даёт 333 ложных на чистом HEAD
(ide-helper рассинхрон локального окружения, память #111); мои файлы phpstan-clean
(app/Services/* = 0, baseline точечно обновлён для 2 Mockery-паттернов).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 16:29:19 +03:00

60 lines
2.6 KiB
PHP

<?php
declare(strict_types=1);
use App\Models\Project;
use App\Models\SupplierProject;
use App\Models\Tenant;
use App\Services\LeadRouter;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class);
uses(SharesSupplierPdo::class);
/**
* BASELINE-страховка под переделку матча источника (pivot → слепок).
* Снимок намеренно содержит signal-поля источника (sms_senders) — чтобы тест
* проходил И на текущем pivot-матче, И после перевода на матч по слепку.
* Если этот тест краснеет после Эпика 2 — переделка сломала доставку по источнику.
*/
it('доставляет лид по поставщиковому sms-источнику при живой связи (baseline)', function () {
Carbon::setTestNow(Carbon::parse('2026-05-28 12:00:00', 'Europe/Moscow')); // до 21:00 → слепок за сегодня
$tenant = Tenant::factory()->create(['balance_rub' => '500.00', 'frozen_by_balance_at' => null]);
$project = Project::factory()->for($tenant)->create([
'is_active' => true,
'signal_type' => 'sms',
'signal_identifier' => null,
'sms_senders' => ['Caranga'],
'sms_keyword' => null,
'delivery_days_mask' => 127,
'daily_limit_target' => 100,
'delivered_today' => 0,
'regions' => [],
]);
$sp = SupplierProject::factory()->create([
'platform' => 'B3',
'signal_type' => 'sms',
'unique_key' => 'Caranga',
]);
DB::table('project_supplier_links')->insert([
'project_id' => $project->id, 'supplier_project_id' => $sp->id,
'platform' => $sp->platform, 'subject_code' => null,
]);
// Снимок за сегодня — с источником в signal-полях (sms_senders), как пишет SnapshotProjectRoutingJob.
DB::table('project_routing_snapshots')->insert([
'snapshot_date' => '2026-05-28', 'project_id' => $project->id, 'tenant_id' => $tenant->id,
'daily_limit' => 10, 'delivery_days_mask' => 127, 'regions' => '{}',
'signal_type' => 'sms', 'signal_identifier' => null,
'sms_senders' => json_encode(['Caranga']), 'sms_keyword' => null,
'expected_volume' => 10, 'delivered_count' => 0, 'created_at' => now(),
]);
$matched = app(LeadRouter::class)->matchEligibleProjects($sp);
expect($matched)->toHaveCount(1);
Carbon::setTestNow();
});