94 lines
3.6 KiB
PHP
94 lines
3.6 KiB
PHP
|
|
<?php
|
|||
|
|
|
|||
|
|
declare(strict_types=1);
|
|||
|
|
|
|||
|
|
use App\Models\SalesAdAudiencePhone;
|
|||
|
|
use App\Models\SalesSmsMessage;
|
|||
|
|
use App\Models\SalesSmsOptout;
|
|||
|
|
use App\Services\Sms\Providers\FakeSmsProvider;
|
|||
|
|
use App\Services\Sms\SmsRecipientSelector;
|
|||
|
|
use App\Services\Sms\SmsRouter;
|
|||
|
|
use Illuminate\Foundation\Testing\DatabaseTransactions;
|
|||
|
|
use Tests\Concerns\SharesSupplierPdo;
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* Отбор получателей СМС (Task 6). Все отсевы — ДО списания денег.
|
|||
|
|
*
|
|||
|
|
* SharesSupplierPdo обязателен: SalesAdAudiencePhone/SalesSmsOptout прибиты к
|
|||
|
|
* pgsql_supplier (отдельный PDO), без шаринга DatabaseTransactions откатывал бы
|
|||
|
|
* только соединение по умолчанию — номера 79990000001–79990000006 утекали бы из
|
|||
|
|
* прогона в прогон и второй запуск падал бы на unique violation.
|
|||
|
|
* Образец — app/tests/Feature/Sales/AdAudienceScreenTest.php:27.
|
|||
|
|
*/
|
|||
|
|
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
|
|||
|
|
|
|||
|
|
/** Отборщик с заглушкой-универсалом. Функцией, а не $this-> — PHPStan не понимает
|
|||
|
|
* привязку $this в Pest и ругается на каждое обращение. */
|
|||
|
|
function sms_selector(): SmsRecipientSelector
|
|||
|
|
{
|
|||
|
|
return new SmsRecipientSelector(new SmsRouter([new FakeSmsProvider]));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
function sms_phone(string $phone, array $overrides = []): SalesAdAudiencePhone
|
|||
|
|
{
|
|||
|
|
return SalesAdAudiencePhone::create(array_merge([
|
|||
|
|
'phone' => $phone,
|
|||
|
|
'expires_at' => now()->addDays(30),
|
|||
|
|
'state' => 'active',
|
|||
|
|
'operator' => 'МТС',
|
|||
|
|
'phone_type' => 'mobile',
|
|||
|
|
], $overrides));
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
it('годный номер попадает в отправку', function () {
|
|||
|
|
sms_phone('79990000001');
|
|||
|
|
|
|||
|
|
$plan = sms_selector()->build(['79990000001']);
|
|||
|
|
|
|||
|
|
expect($plan->sendable)->toHaveCount(1)
|
|||
|
|
->and($plan->skipped)->toBeEmpty();
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('номер из стоп-листа не отправляется никогда', function () {
|
|||
|
|
sms_phone('79990000002');
|
|||
|
|
SalesSmsOptout::create(['phone' => '79990000002', 'reason' => 'manual', 'created_at' => now()]);
|
|||
|
|
|
|||
|
|
$plan = sms_selector()->build(['79990000002']);
|
|||
|
|
|
|||
|
|
expect($plan->sendable)->toBeEmpty()
|
|||
|
|
->and($plan->skipped)->toBe(['79990000002' => SalesSmsMessage::SKIP_OPTOUT]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('городской номер не отправляется — деньги сгорели бы впустую', function () {
|
|||
|
|
sms_phone('79990000003', ['phone_type' => 'landline']);
|
|||
|
|
|
|||
|
|
$plan = sms_selector()->build(['79990000003']);
|
|||
|
|
|
|||
|
|
expect($plan->skipped)->toBe(['79990000003' => SalesSmsMessage::SKIP_NOT_MOBILE]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('погашенный номер не отправляется', function () {
|
|||
|
|
sms_phone('79990000004', ['removed_at' => now()]);
|
|||
|
|
|
|||
|
|
$plan = sms_selector()->build(['79990000004']);
|
|||
|
|
|
|||
|
|
expect($plan->skipped)->toBe(['79990000004' => SalesSmsMessage::SKIP_INACTIVE]);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('дубль внутри одного запуска считается один раз', function () {
|
|||
|
|
sms_phone('79990000005');
|
|||
|
|
|
|||
|
|
$plan = sms_selector()->build(['79990000005', '79990000005']);
|
|||
|
|
|
|||
|
|
expect($plan->sendable)->toHaveCount(1);
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
it('без подходящего провайдера номер помечается без маршрута', function () {
|
|||
|
|
sms_phone('79990000006');
|
|||
|
|
$selector = new SmsRecipientSelector(new SmsRouter([]));
|
|||
|
|
|
|||
|
|
$plan = $selector->build(['79990000006']);
|
|||
|
|
|
|||
|
|
expect($plan->skipped)->toBe(['79990000006' => SalesSmsMessage::SKIP_NO_ROUTE]);
|
|||
|
|
});
|