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

180 lines
8.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;
/**
* Журнал рассылки отдаётся СТРАНИЦАМИ (решение владельца В-203, «делай»).
*
* Зачем: до этого `show` отдавал ВСЕ сообщения рассылки одним куском. На рассылке в
* 20 000 номеров это 20 000 строк в одном ответе — ровно та мина, которую Этап 4 уже
* вынул из базы номеров. Этап 5 сделал её горячее: в журнал добавилась судьба, и
* человек стал открывать его чаще.
*
* 🔴 Итог по судьбам и предложение досыла считаются по ВСЕЙ рассылке, а не по видимой
* странице. Иначе человек, листнув на вторую страницу, увидел бы другой итог — и не
* понял бы, какому верить. Числа называет сервер (В-123), экран их не складывает.
*
* 🪤 В-167: сам по себе `paginate()` берёт номер страницы из ГЛОБАЛЬНОГО запроса
* приложения. По HTTP это то же самое, но проверка через `getJson` дыру не видит
* ВООБЩЕ — поэтому здесь тест спрашивает вторую страницу и сверяет, что строки
* ДРУГИЕ, а не просто «ответ пришёл».
*
* Помощники с префиксом pages* — имена функций в Pest ГЛОБАЛЬНЫЕ.
* Номера только синтетические 7999… — реальные НИКОГДА.
*/
uses(RefreshDatabase::class);
/** Рассылка с заданным числом сообщений. Судьбы — по кругу из $states. */
function pagesCampaign(int $tenantId, int $count, array $states = [null]): ClientSmsCampaign
{
$campaign = ClientSmsCampaign::create([
'tenant_id' => $tenantId,
'title' => 'Страницы',
'body' => 'Текст рассылки',
'sender_name' => 'LIDERRA',
'source' => ClientSmsCampaign::SOURCE_MANUAL,
'status' => ClientSmsCampaign::STATUS_DONE,
'idempotency_key' => 'pages-'.uniqid(),
'segments' => 1,
'planned_count' => $count,
'sent_count' => $count,
'total_sms' => $count,
'price_rub_per_sms' => '8.50',
'estimated_cost_rub' => '8.50',
]);
for ($n = 1; $n <= $count; $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),
'operator' => 'mts',
'provider_key' => 'mts',
'status' => ClientSmsMessage::STATUS_SENT,
'delivery_status' => $states[($n - 1) % count($states)],
'cost_rub' => '8.50',
'segments' => 1,
]);
}
return $campaign;
}
function pagesUser(): User
{
$tenant = Tenant::factory()->create();
return User::factory()->create(['tenant_id' => $tenant->id]);
}
it('отдаёт первую страницу по 50 строк и называет, сколько всего', function () {
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 120);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}");
$response->assertOk();
expect($response->json('messages'))->toHaveCount(50)
->and($response->json('messages_page.total'))->toBe(120)
->and($response->json('messages_page.page'))->toBe(1)
->and($response->json('messages_page.per_page'))->toBe(50)
->and($response->json('messages_page.last_page'))->toBe(3);
});
it('вторая страница отдаёт ДРУГИЕ строки, а не те же самые', function () {
// 🔴 Главный тест этой задачи. Живой прогон 30.07 уже ловил вторую страницу,
// неотличимую от первой (В-167): `paginate()` брал номер страницы из глобального
// запроса, а не из этого. Сверяем именно НОМЕРА строк, а не факт ответа.
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 120);
$first = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}?page=1");
$second = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}?page=2");
$firstIds = array_column($first->json('messages'), 'id');
$secondIds = array_column($second->json('messages'), 'id');
expect($secondIds)->toHaveCount(50)
->and($second->json('messages_page.page'))->toBe(2)
->and(array_intersect($firstIds, $secondIds))->toBe([]);
});
it('последняя страница отдаёт остаток', function () {
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 120);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}?page=3");
expect($response->json('messages'))->toHaveCount(20)
->and($response->json('messages_page.page'))->toBe(3);
});
it('размер страницы не задерёшь адресом — потолок 200', function () {
// Иначе страницы можно обойти одним параметром `?per_page=100000` и вернуться
// ровно туда, откуда ушли (В-166).
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 120);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}?per_page=100000");
expect($response->json('messages'))->toHaveCount(120)
->and($response->json('messages_page.per_page'))->toBe(200);
});
it('кривой размер страницы молча заменяется обычным', function () {
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 60);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}?per_page=0");
// Ругаться не за что: `per_page` набирает не человек, а экран.
expect($response->json('messages'))->toHaveCount(50)
->and($response->json('messages_page.per_page'))->toBe(50);
});
it('итог по судьбам считается по ВСЕЙ рассылке, а не по видимой странице', function () {
// 🔴 Иначе, листнув страницу, человек увидел бы другой итог и не понял бы,
// какому верить. Первая страница — сплошь «доставлено», а в рассылке есть и другие.
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 120, [
SmsDeliveryState::DELIVERED,
SmsDeliveryState::NOT_DELIVERED,
]);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}");
expect($response->json('messages'))->toHaveCount(50)
->and($response->json('delivery.delivered'))->toBe(60)
->and($response->json('delivery.not_delivered'))->toBe(60);
});
it('предложение досыла считается по ВСЕЙ рассылке, а не по видимой странице', function () {
// Досыл — деньги. Считать «не дошедших» по видимой странице значило бы называть
// человеку заниженное число и брать за досыл не ту сумму.
$user = pagesUser();
$campaign = pagesCampaign((int) $user->tenant_id, 120, [
SmsDeliveryState::DELIVERED,
SmsDeliveryState::NOT_DELIVERED,
]);
$response = $this->actingAs($user)->getJson("/api/sms/campaigns/{$campaign->id}");
expect($response->json('resend.can'))->toBeTrue()
->and($response->json('resend.count'))->toBe(60);
});
it('чужую рассылку страницами не открыть', function () {
$mine = pagesUser();
$stranger = pagesUser();
$campaign = pagesCampaign((int) $stranger->tenant_id, 60);
$this->actingAs($mine)->getJson("/api/sms/campaigns/{$campaign->id}?page=2")->assertNotFound();
});