Files
portal/app/tests/Feature/Sales/SmsPerChannelSenderTest.php
T
Дмитрий 509c66e7f9 fix(смс): предпросмотр/оценка учитывают наличие имени канала (§3.4) + убран мёртвый конфиг
Раньше имя отправителя проверялось только в джобе на отправке — предпросмотр
и оценка цены считали такие номера оплачиваемыми. Теперь SmsRecipientSelector
принимает список каналов с активным именем и отсеивает остальные как
skipped_no_sender ещё на отборе — единый источник для preview/store/джоба.

Заодно нашёлся и починен латентный баг: SalesSmsSender::activeByProviderKey()
мержил через Eloquent Collection::merge(), которая перевязывает по первичному
ключу и array_values()-ит результат — терялись строковые ключи keyBy('provider_key').
Починено через collect()->merge() (обычный array_merge, ключи-каналы целы).

Убран мёртвый плоский блок config('services.sms.smsc') — читается только
providers.smsc.
2026-07-23 13:18:45 +03:00

77 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
use App\Jobs\SendSmsCampaignJob;
use App\Models\SalesAdAudiencePhone;
use App\Models\SalesSmsCampaign;
use App\Models\SalesSmsMessage;
use App\Models\SalesSmsSender;
use App\Services\Sms\SmsRecipientSelector;
use App\Services\Sms\SmsRouter;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\Concerns\SharesSupplierPdo;
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
function per_channel_campaign(string $phone): SalesSmsCampaign
{
SalesAdAudiencePhone::create([
'phone' => $phone, 'expires_at' => now()->addDays(30),
'state' => 'active', 'operator' => 'МТС', 'phone_type' => 'mobile',
]);
return SalesSmsCampaign::create([
'title' => 'Прогрев', 'body' => 'Здравствуйте!', 'created_by' => 1,
'status' => SalesSmsCampaign::STATUS_QUEUED,
]);
}
it('в песочнице имя берётся для канала fake', function () {
SalesSmsSender::create(['name' => 'liderra.ru', 'provider_key' => 'fake', 'status' => 'active']);
$campaign = per_channel_campaign('79990000020');
(new SendSmsCampaignJob($campaign->id, ['79990000020']))->handle();
expect($campaign->refresh()->sent_count)->toBe(1);
});
it('нет имени для канала → номер помечается skipped_no_sender, деньги целы', function () {
// Активное имя есть, но для ДРУГОГО канала — для fake имени нет.
SalesSmsSender::create(['name' => 'liderra.ru', 'provider_key' => 'mts', 'status' => 'active']);
$campaign = per_channel_campaign('79990000021');
(new SendSmsCampaignJob($campaign->id, ['79990000021']))->handle();
$row = SalesSmsMessage::where('campaign_id', $campaign->id)->first();
expect($row->status)->toBe(SalesSmsMessage::SKIP_NO_SENDER)
->and($row->cost_kopecks)->toBe(0)
->and($campaign->refresh()->sent_count)->toBe(0);
});
it('не стартует, если ни одного активного имени нет', function () {
SalesSmsSender::create(['name' => 'liderra.ru', 'provider_key' => 'fake', 'status' => 'pending']);
$campaign = per_channel_campaign('79990000022');
(new SendSmsCampaignJob($campaign->id, ['79990000022']))->handle();
expect($campaign->refresh()->status)->toBe(SalesSmsCampaign::STATUS_FAILED)
->and($campaign->last_error)->toContain('имя отправителя');
});
it('отбор исключает номер канала без активного имени (предпросмотр не считает его оплачиваемым)', function () {
// Активное имя есть только для канала mts; в песочнице номер уходит на канал fake — имени нет.
SalesSmsSender::create(['name' => 'liderra.ru', 'provider_key' => 'mts', 'status' => 'active']);
SalesAdAudiencePhone::create([
'phone' => '79990000030', 'expires_at' => now()->addDays(30),
'state' => 'active', 'operator' => 'МТС', 'phone_type' => 'mobile',
]);
$channels = SalesSmsSender::activeByProviderKey(null)->keys()->all();
$selector = new SmsRecipientSelector(app(SmsRouter::class));
$plan = $selector->build(['79990000030'], null, $channels);
expect($plan->sendable)->toBeEmpty()
->and($plan->skipped)->toBe(['79990000030' => SalesSmsMessage::SKIP_NO_SENDER]);
});