Files
portal/app/tests/Feature/ClientSms/DeliveryReporterMtsTest.php
T
Дмитрий 8177cac39b feat(смс-клиент): канал умеет рассказать судьбу сообщения — разъём и читатель МТС
Этап 5, Task 1. Кода, который что-то меняет в базе или в деньгах, здесь нет —
только новая способность канала и её читатель для МТС.

Что появилось:
- SmsDeliveryState — словарь судеб ПОРТАЛА (везу / доставлено / не доставлено /
  оператор не отправил). Один дом на все каналы: у МТС свои слова, у следующего
  оператора будут другие, а экран и деньги обязаны говорить одним языком.
- SmsDeliveryReport — что канал рассказал про одно сообщение.
- SmsDeliveryReporter — НЕОБЯЗАТЕЛЬНАЯ способность канала. Отдельно от SmsProvider
  намеренно: канал, умеющий только отправлять, остаётся годным каналом. Когда у
  Т2, Мегафона и Билайна появятся кабинеты, их файл реализует этот разъём — и
  журнал, экран, деньги, итоги не тронутся ни строчкой (решение владельца 31.07).
- MtsSmsProvider::fetchDelivery — читатель, до 1000 номеров сообщений за запрос.

Форма ответа взята из ЖИВОЙ пробы 30.07 (журнал В-211), а НЕ из документации МТС:
там описан вход по логину-паролю (events_info), а мы ходим по токену и получаем
data[].statuses[]. Тест на выдуманной форме охранял бы выдумку (В-72).

Незнакомое слово оператора НЕ угадывается: судьба остаётся пустой, слово уходит в
журнал. Угадать значило бы соврать про деньги — за «не доставлено» клиенту
возвращаются рубли.

Цена МТС (cost) сохраняется как есть, в рубли не переводится: единицы поля
неизвестны, выдумывать ответ внешней стороны запрещено.

Проверено: 7 тестов зелёные, оба выреза покраснили ровно те тесты, что должны
(подмена NotSent на «не доставлено» и угадывание незнакомого слова). Соседи —
36/36. phpstan — ровно 2 чужие давние, pint чисто.
2026-07-31 05:46:20 +03:00

128 lines
5.3 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Services\Sms\Providers\MtsSmsProvider;
use App\Services\Sms\SmsDeliveryState;
use App\Services\Sms\SmsSendException;
use Illuminate\Support\Facades\Http;
/**
* Строка листа 5.1: по каждому номеру видна настоящая судьба.
*
* 🔴 Форма ответа взята из ЖИВОЙ пробы 30.07 (журнал В-211), а НЕ из документации
* МТС: там описан вход по логину-паролю (`events_info` с `message_id`/`event_at`),
* а мы ходим по токену и получаем `data[].statuses[]`. Тест на выдуманной форме
* охранял бы выдумку (урок В-72: записанный чужой вывод — гипотеза, не факт).
*
* Помощники с префиксом mtsDelivery* — имена функций в Pest ГЛОБАЛЬНЫЕ.
* Телефоны только синтетические 7999… — реальные НИКОГДА.
*/
function mtsDeliveryProvider(): MtsSmsProvider
{
return new MtsSmsProvider('токен', ['mts'], ['*' => 300], 'https://api.mts.test/v1');
}
/**
* Ответ МТС на вопрос о судьбе — ровно той формы, что пришла живьём.
*
* @param array<int, array<string, mixed>> $statuses
* @return array<string, mixed>
*/
function mtsDeliveryBody(string $messageId, array $statuses): array
{
return ['code' => 0, 'data' => [['messageID' => $messageId, 'statuses' => $statuses]]];
}
it('читает доставленное сообщение по живой форме ответа', function () {
Http::fake(['*' => Http::response(mtsDeliveryBody('44747238', [[
'msid' => '79990000001',
'status' => 'Delivered',
'date' => '2026-07-30T16:31:02',
'userDeliveryDate' => '2026-07-30T16:31:40',
'partCount' => 2,
'isViber' => false,
'trafficPatternType' => 'service',
'cost' => 3.4,
]]))]);
$reports = mtsDeliveryProvider()->fetchDelivery(['44747238']);
expect($reports)->toHaveCount(1);
expect($reports[0]->providerMessageId)->toBe('44747238');
expect($reports[0]->phone)->toBe('79990000001');
expect($reports[0]->state)->toBe(SmsDeliveryState::DELIVERED);
expect($reports[0]->deliveredAt?->toDateTimeString())->toBe('2026-07-30 16:31:40');
expect($reports[0]->partCount)->toBe(2);
expect($reports[0]->providerCost)->toBe('3.4');
});
it('читает «принял, но не отправил» — тот самый случай живой пробы 30.07', function () {
Http::fake(['*' => Http::response(mtsDeliveryBody('44747239', [[
'msid' => '79990000002',
'status' => 'NotSent',
'date' => '2026-07-30T16:31:02',
'userDeliveryDate' => null,
'partCount' => 1,
'cost' => 0,
]]))]);
$reports = mtsDeliveryProvider()->fetchDelivery(['44747239']);
expect($reports[0]->state)->toBe(SmsDeliveryState::NOT_SENT);
expect($reports[0]->deliveredAt)->toBeNull();
expect($reports[0]->providerCost)->toBe('0');
});
it('читает «везу» и «не довёз» как разные судьбы', function () {
Http::fake(['*' => Http::response(['code' => 0, 'data' => [
['messageID' => '1', 'statuses' => [['msid' => '79990000003', 'status' => 'Sending']]],
['messageID' => '2', 'statuses' => [['msid' => '79990000004', 'status' => 'NotDelivered']]],
]])]);
$reports = mtsDeliveryProvider()->fetchDelivery(['1', '2']);
expect($reports[0]->state)->toBe(SmsDeliveryState::SENDING);
expect($reports[1]->state)->toBe(SmsDeliveryState::NOT_DELIVERED);
});
it('незнакомое слово оператора не угадывает, а отдаёт пустую судьбу и само слово', function () {
Http::fake(['*' => Http::response(mtsDeliveryBody('1', [[
'msid' => '79990000005',
'status' => 'СовершенноНовоеСлово',
'partCount' => 1,
]]))]);
$reports = mtsDeliveryProvider()->fetchDelivery(['1']);
expect($reports[0]->state)->toBeNull();
expect($reports[0]->rawState)->toBe('СовершенноНовоеСлово');
});
it('спрашивает номера сообщений одной строкой через запятую', function () {
Http::fake(['*' => Http::response(['code' => 0, 'data' => []])]);
mtsDeliveryProvider()->fetchDelivery(['1', '2', '3']);
Http::assertSent(fn ($request) => str_contains(urldecode($request->url()), 'messageIDs=1,2,3'));
});
it('пустой список никого не спрашивает вовсе', function () {
Http::fake(['*' => Http::response(['code' => 0, 'data' => []])]);
expect(mtsDeliveryProvider()->fetchDelivery([]))->toBe([]);
Http::assertNothingSent();
});
it('отказ МТС — не окончательный: судьбу всегда можно переспросить позже', function () {
Http::fake(['*' => Http::response('', 502)]);
try {
mtsDeliveryProvider()->fetchDelivery(['1']);
$this->fail('ожидали отказ канала');
} catch (SmsSendException $e) {
expect($e->terminal)->toBeFalse();
}
});