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

167 lines
7.6 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\ClientSmsCampaign;
use App\Models\ClientSmsMessage;
use App\Models\Tenant;
use App\Models\User;
use App\Services\Sms\SmsDeliveryState;
use Illuminate\Foundation\Testing\RefreshDatabase;
/**
* Строка листа 5.3: итог рассылки — «доставлено 812, не доставлено 74, ещё в пути 14»,
* а не «отправлено 900».
*
* До Этапа 5 человек видел только «отправлено»: то есть сколько сообщений мы ОТДАЛИ
* оператору. Дошли ли они до людей — экран не знал вовсе, а платит клиент именно за
* то, чтобы дошли.
*
* 🪤 «Ещё в пути» — это И явное `sending`, И те, кого мы просто ещё не спрашивали
* (графа пустая). Для человека это одно состояние «пока не знаем» (моё решение В-214).
*
* Помощники с префиксом totals* — имена функций в Pest ГЛОБАЛЬНЫЕ.
* Номера только синтетические 7999… — реальные НИКОГДА.
*/
uses(RefreshDatabase::class);
/**
* Рассылка, у сообщений которой судьбы заданы списком.
*
* @param array<int, string|null> $states
*/
function totalsCampaignWith(int $tenantId, array $states, string $status = ClientSmsMessage::STATUS_SENT): ClientSmsCampaign
{
$campaign = ClientSmsCampaign::create([
'tenant_id' => $tenantId,
'title' => 'Итог',
'body' => 'Текст рассылки',
'sender_name' => 'LIDERRA',
'source' => ClientSmsCampaign::SOURCE_MANUAL,
'status' => ClientSmsCampaign::STATUS_DONE,
'idempotency_key' => 'totals-'.uniqid(),
'segments' => 1,
'planned_count' => count($states),
'sent_count' => count($states),
'total_sms' => count($states),
'price_rub_per_sms' => '8.50',
'estimated_cost_rub' => '8.50',
]);
$n = 0;
foreach ($states as $state) {
$n++;
ClientSmsMessage::create([
'tenant_id' => $tenantId,
'campaign_id' => $campaign->id,
'phone' => '7999'.str_pad((string) $campaign->id, 4, '0', STR_PAD_LEFT).str_pad((string) $n, 3, '0', STR_PAD_LEFT),
'provider_key' => 'mts',
'status' => $status,
'delivery_status' => $state,
'cost_rub' => '8.50',
'segments' => 1,
]);
}
return $campaign;
}
it('карточка рассылки показывает доставлено, не доставлено и ещё в пути', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
$campaign = totalsCampaignWith((int) $tenant->id, [
SmsDeliveryState::DELIVERED,
SmsDeliveryState::DELIVERED,
SmsDeliveryState::NOT_DELIVERED,
SmsDeliveryState::SENDING,
null,
]);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}");
$response->assertOk();
expect($response->json('delivery.delivered'))->toBe(2);
expect($response->json('delivery.not_delivered'))->toBe(1);
// «Ещё в пути» — это и явное `sending`, и те, кого мы ещё не спрашивали.
expect($response->json('delivery.on_the_way'))->toBe(2);
});
it('список рассылок несёт итог по КАЖДОЙ — человек видит его не открывая карточку', function () {
// 🔴 Рассылок в списке намеренно ДВЕ и с разными числами: со списком из одной
// штуки этот тест не отличал бы «итог по своей рассылке» от «итог по первой в
// списке» — и не покраснел бы на такой подмене вовсе (урок В-219).
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
$first = totalsCampaignWith((int) $tenant->id, [SmsDeliveryState::DELIVERED, SmsDeliveryState::NOT_DELIVERED]);
$second = totalsCampaignWith((int) $tenant->id, [
SmsDeliveryState::DELIVERED,
SmsDeliveryState::DELIVERED,
SmsDeliveryState::DELIVERED,
SmsDeliveryState::SENDING,
]);
$response = $this->actingAs($user)->getJson('/api/sms/campaigns');
$response->assertOk();
// Список идёт от новых к старым: сначала вторая рассылка, потом первая.
$rows = collect($response->json('campaigns'))->keyBy('id');
expect($rows[$first->id]['delivery']['delivered'])->toBe(1);
expect($rows[$first->id]['delivery']['not_delivered'])->toBe(1);
expect($rows[$second->id]['delivery']['delivered'])->toBe(3);
expect($rows[$second->id]['delivery']['on_the_way'])->toBe(1);
});
it('«оператор не отправил вовсе» считается отдельно от «не доставлено»', function () {
// Это разные события, и разговор с оператором по ним разный (В-212): за
// «не отправил вовсе» клиент платить не должен тем более. Свалить их в одну
// кучу значило бы потерять повод для этого разговора.
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
$campaign = totalsCampaignWith((int) $tenant->id, [
SmsDeliveryState::NOT_SENT,
SmsDeliveryState::NOT_DELIVERED,
]);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}");
$response->assertOk();
expect($response->json('delivery.not_sent'))->toBe(1);
expect($response->json('delivery.not_delivered'))->toBe(1);
});
it('пробные сообщения песочницы в итог не идут', function () {
// За `fake_sent` никто никуда не ехал и денег не платил — ровно так же их не
// считает месячный объём (`ClientSmsVolumeCounter`). Посчитай мы их «ещё в пути»,
// рассылка в песочнице никогда не стала бы окончательной.
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
$campaign = totalsCampaignWith(
(int) $tenant->id,
[null, SmsDeliveryState::DELIVERED],
ClientSmsMessage::STATUS_FAKE_SENT,
);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}");
$response->assertOk();
expect($response->json('delivery.on_the_way'))->toBe(0);
expect($response->json('delivery.delivered'))->toBe(0);
});
it('итог соседней рассылки в карточку не подмешивается', function () {
$tenant = Tenant::factory()->create();
$user = User::factory()->create(['tenant_id' => $tenant->id]);
$mine = totalsCampaignWith((int) $tenant->id, [SmsDeliveryState::DELIVERED]);
totalsCampaignWith((int) $tenant->id, [
SmsDeliveryState::DELIVERED,
SmsDeliveryState::DELIVERED,
SmsDeliveryState::DELIVERED,
]);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$mine->id}");
$response->assertOk();
expect($response->json('delivery.delivered'))->toBe(1);
});