2026-07-27 16:15:17 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
declare(strict_types=1);
|
|
|
|
|
|
|
|
|
|
use App\Models\ClientTg\Campaign;
|
|
|
|
|
use App\Models\ClientTg\CampaignPhone;
|
|
|
|
|
use App\Models\ClientTg\Contact;
|
|
|
|
|
use App\Models\ClientTg\Optout;
|
|
|
|
|
use App\Models\Deal;
|
|
|
|
|
use App\Models\Tenant;
|
|
|
|
|
use App\Services\ClientTg\TelegramAudienceService;
|
|
|
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
|
|
|
|
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
|
|
|
|
|
|
function makeTgCampaign(int $tenantId, string $kind, ?array $params = null): Campaign
|
|
|
|
|
{
|
|
|
|
|
return Campaign::create([
|
|
|
|
|
'tenant_id' => $tenantId,
|
|
|
|
|
'status' => Campaign::STATUS_DRAFT,
|
|
|
|
|
'ad_text' => 'Объявление',
|
|
|
|
|
'ad_link' => 'https://liderra.ru',
|
|
|
|
|
'media_path' => null,
|
|
|
|
|
'ord_category' => 'Размещение рекламы',
|
|
|
|
|
'budget_cap_rub' => '1000.00',
|
|
|
|
|
'audience_kind' => $kind,
|
|
|
|
|
'audience_params' => $params,
|
|
|
|
|
'created_by' => null,
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
it('собирает кандидатов из сделок (окно, мягкое удаление, мастер-лид)', function () {
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenant->id, 'phone' => '79990000001',
|
|
|
|
|
'phone_operator' => 'МТС', 'received_at' => now()->subDays(2),
|
|
|
|
|
]);
|
|
|
|
|
// вне окна
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenant->id, 'phone' => '79990000002',
|
|
|
|
|
'phone_operator' => 'МТС', 'received_at' => now()->subDays(30),
|
|
|
|
|
]);
|
|
|
|
|
// мягко удалён
|
|
|
|
|
$deleted = Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenant->id, 'phone' => '79990000003',
|
|
|
|
|
'phone_operator' => 'МТС', 'received_at' => now()->subDays(1),
|
|
|
|
|
]);
|
|
|
|
|
$deleted->delete();
|
|
|
|
|
// дубликат другого лида
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenant->id, 'phone' => '79990000004',
|
|
|
|
|
'phone_operator' => 'МТС', 'duplicate_of_id' => 999999, 'received_at' => now()->subDays(1),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$campaign = makeTgCampaign($tenant->id, Campaign::AUDIENCE_DEALS, ['days' => 5]);
|
|
|
|
|
|
|
|
|
|
$audience = app(TelegramAudienceService::class)->build($campaign);
|
|
|
|
|
|
|
|
|
|
expect($audience->phones)->toBe(['79990000001'])
|
|
|
|
|
->and($audience->candidatesCount)->toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('собирает кандидатов из своей базы (base)', function () {
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000011', 'name' => 'Иван', 'operator' => 'Билайн']);
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000012', 'name' => null, 'operator' => null]);
|
|
|
|
|
|
|
|
|
|
$campaign = makeTgCampaign($tenant->id, Campaign::AUDIENCE_BASE);
|
|
|
|
|
|
|
|
|
|
$audience = app(TelegramAudienceService::class)->build($campaign);
|
|
|
|
|
|
|
|
|
|
expect($audience->phones)->toEqualCanonicalizing(['79990000011', '79990000012'])
|
|
|
|
|
->and($audience->candidatesCount)->toBe(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('собирает кандидатов из своего списка с учётом срока (list)', function () {
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
$campaign = makeTgCampaign($tenant->id, Campaign::AUDIENCE_LIST);
|
|
|
|
|
|
|
|
|
|
CampaignPhone::create(['tenant_id' => $tenant->id, 'campaign_id' => $campaign->id, 'phone' => '79990000021', 'expires_at' => null]);
|
|
|
|
|
CampaignPhone::create(['tenant_id' => $tenant->id, 'campaign_id' => $campaign->id, 'phone' => '79990000022', 'expires_at' => now()->addDays(3)]);
|
|
|
|
|
// истёкший — не должен попасть
|
|
|
|
|
CampaignPhone::create(['tenant_id' => $tenant->id, 'campaign_id' => $campaign->id, 'phone' => '79990000023', 'expires_at' => now()->subDay()]);
|
|
|
|
|
|
|
|
|
|
$audience = app(TelegramAudienceService::class)->build($campaign);
|
|
|
|
|
|
|
|
|
|
expect($audience->phones)->toEqualCanonicalizing(['79990000021', '79990000022'])
|
|
|
|
|
->and($audience->candidatesCount)->toBe(2);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('нормализует форматы и схлопывает дубли до голых 11 цифр', function () {
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenant->id, 'phone' => '89991112233',
|
|
|
|
|
'phone_operator' => null, 'received_at' => now()->subDays(1),
|
|
|
|
|
]);
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenant->id, 'phone' => '+79991112233',
|
|
|
|
|
'phone_operator' => 'МегаФон', 'received_at' => now()->subDays(2),
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
$campaign = makeTgCampaign($tenant->id, Campaign::AUDIENCE_DEALS, ['days' => 5]);
|
|
|
|
|
|
|
|
|
|
$audience = app(TelegramAudienceService::class)->build($campaign);
|
|
|
|
|
|
|
|
|
|
expect($audience->phones)->toBe(['79991112233'])
|
|
|
|
|
->and($audience->candidatesCount)->toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('применяет стоп-лист (optout)', function () {
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000041', 'name' => null, 'operator' => null]);
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000042', 'name' => null, 'operator' => null]);
|
|
|
|
|
Optout::create(['tenant_id' => $tenant->id, 'phone' => '79990000042']);
|
|
|
|
|
|
|
|
|
|
$campaign = makeTgCampaign($tenant->id, Campaign::AUDIENCE_BASE);
|
|
|
|
|
|
|
|
|
|
$audience = app(TelegramAudienceService::class)->build($campaign);
|
|
|
|
|
|
|
|
|
|
expect($audience->phones)->toBe(['79990000041'])
|
|
|
|
|
->and($audience->candidatesCount)->toBe(1);
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-28 06:37:25 +03:00
|
|
|
it('стоп-лист в любом формате вычищает нормализованного кандидата', function () {
|
|
|
|
|
$tenant = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
// кандидаты — голый 11-значный вид (как хранит своя база)
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000041', 'name' => null, 'operator' => null]);
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000042', 'name' => null, 'operator' => null]);
|
|
|
|
|
Contact::create(['tenant_id' => $tenant->id, 'phone' => '79990000043', 'name' => null, 'operator' => null]);
|
|
|
|
|
|
|
|
|
|
// отписки в «грязных» форматах (в рамках реального ограничения схемы phone varchar(11):
|
|
|
|
|
// строку "+7 999 000-00-41" туда физически не записать — берём формы, которые влезают)
|
|
|
|
|
Optout::create(['tenant_id' => $tenant->id, 'phone' => '89990000041']); // 8-префикс, 11 цифр
|
|
|
|
|
Optout::create(['tenant_id' => $tenant->id, 'phone' => '9990000042']); // без кода страны, 10 цифр
|
|
|
|
|
|
|
|
|
|
$audience = app(TelegramAudienceService::class)->build(
|
|
|
|
|
makeTgCampaign($tenant->id, Campaign::AUDIENCE_BASE)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
expect($audience->phones)->not->toContain('79990000041')
|
|
|
|
|
->and($audience->phones)->not->toContain('79990000042')
|
|
|
|
|
->and($audience->phones)->toContain('79990000043');
|
|
|
|
|
});
|
|
|
|
|
|
2026-07-27 16:15:17 +03:00
|
|
|
it('никогда не смешивает номера разных тенантов', function () {
|
|
|
|
|
$tenantA = Tenant::factory()->create();
|
|
|
|
|
$tenantB = Tenant::factory()->create();
|
|
|
|
|
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenantA->id, 'phone' => '79990000031',
|
|
|
|
|
'phone_operator' => 'МТС', 'received_at' => now()->subDays(1),
|
|
|
|
|
]);
|
|
|
|
|
Deal::factory()->create([
|
|
|
|
|
'tenant_id' => $tenantB->id, 'phone' => '79990000032',
|
|
|
|
|
'phone_operator' => 'МТС', 'received_at' => now()->subDays(1),
|
|
|
|
|
]);
|
|
|
|
|
Contact::create(['tenant_id' => $tenantB->id, 'phone' => '79990000033', 'name' => null, 'operator' => null]);
|
|
|
|
|
|
|
|
|
|
$deals = app(TelegramAudienceService::class)->build(makeTgCampaign($tenantA->id, Campaign::AUDIENCE_DEALS, ['days' => 5]));
|
|
|
|
|
$base = app(TelegramAudienceService::class)->build(makeTgCampaign($tenantA->id, Campaign::AUDIENCE_BASE));
|
|
|
|
|
|
|
|
|
|
expect($deals->phones)->toBe(['79990000031'])
|
|
|
|
|
->and($base->phones)->toBe([]);
|
|
|
|
|
});
|