593030271b
Имя согласовывает ОПЕРАТОР, и у каждого оно своё. Клиентская рассылка несла одно имя всем каналам, а умолчанием ему служило `services.sms.mts.naming` — то есть имя, согласованное с МТС, подставлялось Теле2 и подставилось бы Мегафону и Билайну. Живой отказ Теле2 `invalid_source_address` 04.08.2026 — ровно этот механизм. На бою у клиентов заведено НОЛЬ своих имён, значит это касалось каждой отправки. Появился ClientSmsSenderResolver: спрашивает имя у той сети, чей номер. Пустая строка на выходе — не «имени нет», а «своего имени для этой сети нет, канал подпишется собственным согласованным именем из настроек». Переведены на него оба места отправки: рассылка (SendClientSmsCampaignJob) и авто-СМС по сделке (SendAutoSmsForDealJob). Универсальный СМС-центр передавал имя КАК ЕСТЬ — теперь тоже падает на своё (SMSC_NAMING). Раньше пустое имя туда не приходило никогда, с этой правкой стало бы приходить: ушли бы СМС без отправителя. Заплатка внутри канала Теле2 (приведение написания к своему регистру) остаётся, но больше не несущая: она чинила следствие в одном канале, а причина была общая. Экранное имя (ResolvesClientSmsSenderName) НЕ трогали — это запись намерения, а не то, чем подпишется сеть; в его шапке теперь написано почему, чтобы не «починили» обратно. 🪤 Урок по дороге: авто-СМС ловит любую ошибку и молча выходит. Справочник имён был передан не в ту функцию — вместо падения получилась ТИШИНА, не уходило ничего. Поймали три чужих теста, до того зелёных. Проверено: 4614 тестов, упал один чужой (ExampleTest — в этой папке не собран фронтенд). Статанализ 0. Стиль правленых файлов чист. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
94 lines
3.2 KiB
PHP
94 lines
3.2 KiB
PHP
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
use App\Services\Sms\Providers\SmscSmsProvider;
|
||
use App\Services\Sms\SmsOutgoing;
|
||
use App\Services\Sms\SmsSendException;
|
||
use Illuminate\Support\Facades\Http;
|
||
|
||
function smsc_provider(): SmscSmsProvider
|
||
{
|
||
return new SmscSmsProvider('login', 'secret', ['*'], ['*' => 300]);
|
||
}
|
||
|
||
function smsc_message(): SmsOutgoing
|
||
{
|
||
return new SmsOutgoing(
|
||
phone: '79990000001',
|
||
body: 'Здравствуйте!',
|
||
senderName: 'liderra.ru',
|
||
operator: 'МегаФон',
|
||
segments: 1,
|
||
);
|
||
}
|
||
|
||
it('обслуживает всех как универсал', function () {
|
||
expect(smsc_provider()->servesOperators())->toBe(['*'])
|
||
->and(smsc_provider()->key())->toBe('smsc');
|
||
});
|
||
|
||
/**
|
||
* С 05.08.2026 имя отправителя выбирается ПО ОПЕРАТОРУ получателя, и пустая
|
||
* строка означает «своего имени у клиента для этой сети нет — подпишись своим».
|
||
* Универсал же передавал имя КАК ЕСТЬ и ушёл бы с пустым отправителем.
|
||
*/
|
||
it('пустое имя заменяет своим согласованным', function () {
|
||
Http::fake(['smsc.ru/*' => Http::response(['id' => 777, 'cnt' => 1], 200)]);
|
||
|
||
$provider = new SmscSmsProvider('login', 'secret', ['*'], ['*' => 300], 'liderra.ru');
|
||
|
||
$provider->send(new SmsOutgoing(
|
||
phone: '79990000001',
|
||
body: 'Здравствуйте!',
|
||
senderName: '',
|
||
operator: 'МегаФон',
|
||
segments: 1,
|
||
));
|
||
|
||
Http::assertSent(fn ($request) => $request['sender'] === 'liderra.ru');
|
||
});
|
||
|
||
it('заданное имя клиента своим не подменяет', function () {
|
||
Http::fake(['smsc.ru/*' => Http::response(['id' => 778, 'cnt' => 1], 200)]);
|
||
|
||
$provider = new SmscSmsProvider('login', 'secret', ['*'], ['*' => 300], 'liderra.ru');
|
||
|
||
$provider->send(new SmsOutgoing(
|
||
phone: '79990000001',
|
||
body: 'Здравствуйте!',
|
||
senderName: 'mybrand.ru',
|
||
operator: 'МегаФон',
|
||
segments: 1,
|
||
));
|
||
|
||
Http::assertSent(fn ($request) => $request['sender'] === 'mybrand.ru');
|
||
});
|
||
|
||
it('успешная отправка возвращает id сообщения и цену по сегментам', function () {
|
||
Http::fake(['smsc.ru/*' => Http::response(['id' => 555, 'cnt' => 1], 200)]);
|
||
|
||
$result = smsc_provider()->send(smsc_message());
|
||
|
||
expect($result->providerMessageId)->toBe('555')
|
||
->and($result->costKopecks)->toBe(300);
|
||
});
|
||
|
||
it('отказ smsc.ru бросает terminal-исключение', function () {
|
||
Http::fake(['smsc.ru/*' => Http::response(['error' => 'invalid number', 'error_code' => 7], 200)]);
|
||
|
||
expect(fn () => smsc_provider()->send(smsc_message()))
|
||
->toThrow(SmsSendException::class);
|
||
});
|
||
|
||
it('сетевой сбой — не terminal (можно повторить)', function () {
|
||
Http::fake(['smsc.ru/*' => Http::response('', 500)]);
|
||
|
||
try {
|
||
smsc_provider()->send(smsc_message());
|
||
$this->fail('ожидалось SmsSendException');
|
||
} catch (SmsSendException $e) {
|
||
expect($e->terminal)->toBeFalse();
|
||
}
|
||
});
|