Files
portal/app/tests/Feature/ClientSms/AudienceBuilderTest.php
T

197 lines
6.4 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\ClientSmsCampaign;
use App\Models\ClientSmsCampaignPhone;
use App\Models\ClientSmsContact;
use App\Models\Deal;
use App\Models\Tenant;
use App\Services\ClientSms\ClientSmsAudienceBuilder;
use Illuminate\Foundation\Testing\RefreshDatabase;
uses(RefreshDatabase::class);
function makeClientSmsCampaign(int $tenantId, string $source, ?int $audienceDays = null): ClientSmsCampaign
{
return ClientSmsCampaign::create([
'tenant_id' => $tenantId,
'title' => 'Т',
'body' => 'Здравствуйте!',
'sender_name' => 'LIDERRA',
'source' => $source,
'audience_days' => $audienceDays,
'status' => ClientSmsCampaign::STATUS_DRAFT,
'segments' => 1,
'planned_count' => 0,
'sent_count' => 0,
'total_sms' => 0,
'price_rub_per_sms' => '8.50',
'estimated_cost_rub' => '0.00',
'actual_cost_rub' => null,
'created_by' => null,
]);
}
it('builds audience from deals honoring window, soft-delete and duplicate master', 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();
// дубликат другого лида (duplicate_of_id не null) — не должен попасть
Deal::factory()->create([
'tenant_id' => $tenant->id,
'phone' => '79990000004',
'phone_operator' => 'МТС',
'duplicate_of_id' => 999999,
'received_at' => now()->subDays(1),
]);
$campaign = makeClientSmsCampaign($tenant->id, ClientSmsCampaign::SOURCE_DEALS, 5);
$result = app(ClientSmsAudienceBuilder::class)->build($campaign);
expect($result)->toBe([
['phone' => '79990000001', 'operator' => 'МТС'],
]);
});
it('builds audience from own contact base (source=base)', function () {
$tenant = Tenant::factory()->create();
ClientSmsContact::create([
'tenant_id' => $tenant->id,
'phone' => '79990000011',
'name' => 'Иван',
'operator' => 'Билайн',
]);
ClientSmsContact::create([
'tenant_id' => $tenant->id,
'phone' => '79990000012',
'name' => 'Пётр',
'operator' => null,
]);
$campaign = makeClientSmsCampaign($tenant->id, ClientSmsCampaign::SOURCE_BASE);
$result = app(ClientSmsAudienceBuilder::class)->build($campaign);
expect($result)->toEqualCanonicalizing([
['phone' => '79990000011', 'operator' => 'Билайн'],
['phone' => '79990000012', 'operator' => null],
]);
});
it('builds audience from manual campaign phone list honoring expiry (source=manual)', function () {
$tenant = Tenant::factory()->create();
$campaign = makeClientSmsCampaign($tenant->id, ClientSmsCampaign::SOURCE_MANUAL);
ClientSmsCampaignPhone::create([
'tenant_id' => $tenant->id,
'campaign_id' => $campaign->id,
'phone' => '79990000021',
'expires_at' => null,
]);
ClientSmsCampaignPhone::create([
'tenant_id' => $tenant->id,
'campaign_id' => $campaign->id,
'phone' => '79990000022',
'expires_at' => now()->addDays(3),
]);
// истёкший — не должен попасть
ClientSmsCampaignPhone::create([
'tenant_id' => $tenant->id,
'campaign_id' => $campaign->id,
'phone' => '79990000023',
'expires_at' => now()->subDay(),
]);
$result = app(ClientSmsAudienceBuilder::class)->build($campaign);
expect($result)->toEqualCanonicalizing([
['phone' => '79990000021', 'operator' => null],
['phone' => '79990000022', 'operator' => null],
]);
});
it('never leaks phones across tenants', 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),
]);
ClientSmsContact::create([
'tenant_id' => $tenantB->id,
'phone' => '79990000033',
'name' => null,
'operator' => null,
]);
$campaignDeals = makeClientSmsCampaign($tenantA->id, ClientSmsCampaign::SOURCE_DEALS, 5);
$campaignBase = makeClientSmsCampaign($tenantA->id, ClientSmsCampaign::SOURCE_BASE);
$resultDeals = app(ClientSmsAudienceBuilder::class)->build($campaignDeals);
$resultBase = app(ClientSmsAudienceBuilder::class)->build($campaignBase);
expect($resultDeals)->toBe([
['phone' => '79990000031', 'operator' => 'МТС'],
])
->and($resultBase)->toBe([]);
});
it('dedups phones across formats preferring a non-null operator, normalized to bare 11 digits', 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 = makeClientSmsCampaign($tenant->id, ClientSmsCampaign::SOURCE_DEALS, 5);
$result = app(ClientSmsAudienceBuilder::class)->build($campaign);
expect($result)->toBe([
['phone' => '79991112233', 'operator' => 'МегаФон'],
]);
});