162 lines
6.6 KiB
PHP
162 lines
6.6 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
use App\Models\ClientSmsCampaign;
|
||
|
|
use App\Models\ClientSmsMessage;
|
||
|
|
use App\Models\Tenant;
|
||
|
|
use App\Models\User;
|
||
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||
|
|
use Illuminate\Support\Carbon;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Журнал автоматических СМС на экране клиента (строка листа 4.11, решение
|
||
|
|
* владельца В-102 «обязательно нужен»).
|
||
|
|
*
|
||
|
|
* Авто-СМС не принадлежат ни одной рассылке (`campaign_id` пуст), поэтому их не
|
||
|
|
* показывает ни один существующий адрес портала — причина записана в базу, а
|
||
|
|
* человеку не видна. Здесь проверяется, что видна.
|
||
|
|
*
|
||
|
|
* Синтетические номера 7999… — реальные НИКОГДА.
|
||
|
|
*/
|
||
|
|
uses(RefreshDatabase::class);
|
||
|
|
|
||
|
|
beforeEach(function () {
|
||
|
|
Carbon::setTestNow('2026-08-15 12:00:00');
|
||
|
|
|
||
|
|
$this->tenant = Tenant::factory()->create();
|
||
|
|
$this->user = User::factory()->create(['tenant_id' => $this->tenant->id]);
|
||
|
|
$this->actingAs($this->user);
|
||
|
|
});
|
||
|
|
|
||
|
|
afterEach(function () {
|
||
|
|
Carbon::setTestNow();
|
||
|
|
});
|
||
|
|
|
||
|
|
/** Авто-СМС: сообщение без кампании. */
|
||
|
|
function autoLogMessage(int $tenantId, string $phone, string $status, ?string $createdAt = null): ClientSmsMessage
|
||
|
|
{
|
||
|
|
$message = ClientSmsMessage::create([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'campaign_id' => null,
|
||
|
|
'phone' => $phone,
|
||
|
|
'status' => $status,
|
||
|
|
'cost_rub' => $status === ClientSmsMessage::STATUS_SENT ? '8.50' : '0.00',
|
||
|
|
'segments' => 1,
|
||
|
|
]);
|
||
|
|
|
||
|
|
if ($createdAt !== null) {
|
||
|
|
// Через модель дата перезапишется текущим временем — правим запросом.
|
||
|
|
ClientSmsMessage::whereKey($message->id)->update(['created_at' => $createdAt]);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $message;
|
||
|
|
}
|
||
|
|
|
||
|
|
it('журнал показывает только авто-СМС и только за последние 30 дней', function () {
|
||
|
|
$tenantId = (int) $this->tenant->id;
|
||
|
|
|
||
|
|
autoLogMessage($tenantId, '79990000001', ClientSmsMessage::STATUS_SENT);
|
||
|
|
autoLogMessage($tenantId, '79990000002', ClientSmsMessage::SKIP_OPTOUT);
|
||
|
|
autoLogMessage($tenantId, '79990000003', ClientSmsMessage::SKIP_NO_FUNDS);
|
||
|
|
|
||
|
|
// Сообщение РАССЫЛКИ в журнал авто-СМС попадать не должно: у него есть кампания.
|
||
|
|
$campaign = ClientSmsCampaign::create([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'title' => 'Рассылка',
|
||
|
|
'body' => 'Здравствуйте!',
|
||
|
|
'sender_name' => 'liderra.ru',
|
||
|
|
'source' => ClientSmsCampaign::SOURCE_BASE,
|
||
|
|
'status' => ClientSmsCampaign::STATUS_QUEUED,
|
||
|
|
'segments' => 1,
|
||
|
|
'planned_count' => 1,
|
||
|
|
'total_sms' => 1,
|
||
|
|
'price_rub_per_sms' => '8.50',
|
||
|
|
'estimated_cost_rub' => '8.50',
|
||
|
|
'created_by' => null,
|
||
|
|
]);
|
||
|
|
ClientSmsMessage::create([
|
||
|
|
'tenant_id' => $tenantId,
|
||
|
|
'campaign_id' => $campaign->id,
|
||
|
|
'phone' => '79990000004',
|
||
|
|
'status' => ClientSmsMessage::STATUS_SENT,
|
||
|
|
'cost_rub' => '8.50',
|
||
|
|
'segments' => 1,
|
||
|
|
]);
|
||
|
|
|
||
|
|
// Авто-СМС старше 30 дней — тоже не должно.
|
||
|
|
autoLogMessage($tenantId, '79990000005', ClientSmsMessage::STATUS_SENT, '2026-06-01 10:00:00');
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/sms/auto-log');
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
expect($response->json('totals.sent'))->toBe(1)
|
||
|
|
->and($response->json('totals.skipped'))->toBe(2)
|
||
|
|
->and(collect($response->json('reasons'))->pluck('count', 'reason')->all())
|
||
|
|
->toBe([ClientSmsMessage::SKIP_NO_FUNDS => 1, ClientSmsMessage::SKIP_OPTOUT => 1])
|
||
|
|
->and(collect($response->json('messages'))->pluck('phone')->all())
|
||
|
|
->toBe(['79990000003', '79990000002', '79990000001']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('пробное сообщение считается НЕ ушедшим (В-172)', function () {
|
||
|
|
$tenantId = (int) $this->tenant->id;
|
||
|
|
|
||
|
|
autoLogMessage($tenantId, '79990000011', ClientSmsMessage::STATUS_SENT);
|
||
|
|
autoLogMessage($tenantId, '79990000012', ClientSmsMessage::STATUS_FAKE_SENT);
|
||
|
|
autoLogMessage($tenantId, '79990000013', ClientSmsMessage::STATUS_FAILED);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/sms/auto-log');
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
// Пробное не ушло абоненту, и денежный счётчик модуля его тоже не считает.
|
||
|
|
expect($response->json('totals.sent'))->toBe(1)
|
||
|
|
->and($response->json('totals.skipped'))->toBe(2)
|
||
|
|
->and(collect($response->json('reasons'))->pluck('count', 'reason')->all())
|
||
|
|
->toBe([
|
||
|
|
ClientSmsMessage::STATUS_FAILED => 1,
|
||
|
|
ClientSmsMessage::STATUS_FAKE_SENT => 1,
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('журнал чужого клиента не показывает', function () {
|
||
|
|
$tenantId = (int) $this->tenant->id;
|
||
|
|
|
||
|
|
$stranger = Tenant::factory()->create();
|
||
|
|
autoLogMessage((int) $stranger->id, '79990000021', ClientSmsMessage::STATUS_SENT);
|
||
|
|
autoLogMessage((int) $stranger->id, '79990000022', ClientSmsMessage::SKIP_NO_FUNDS);
|
||
|
|
|
||
|
|
autoLogMessage($tenantId, '79990000023', ClientSmsMessage::STATUS_SENT);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/sms/auto-log');
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
expect($response->json('totals.sent'))->toBe(1)
|
||
|
|
->and($response->json('totals.skipped'))->toBe(0)
|
||
|
|
->and(collect($response->json('messages'))->pluck('phone')->all())
|
||
|
|
->toBe(['79990000023']);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('числа считаются по всем 30 дням, а список событий урезан сотней (В-170)', function () {
|
||
|
|
$tenantId = (int) $this->tenant->id;
|
||
|
|
|
||
|
|
for ($i = 1; $i <= 105; $i++) {
|
||
|
|
autoLogMessage($tenantId, '7999000'.str_pad((string) $i, 4, '0', STR_PAD_LEFT), ClientSmsMessage::STATUS_SENT);
|
||
|
|
}
|
||
|
|
autoLogMessage($tenantId, '79990009999', ClientSmsMessage::SKIP_NO_FUNDS);
|
||
|
|
|
||
|
|
$response = $this->getJson('/api/sms/auto-log');
|
||
|
|
|
||
|
|
$response->assertOk();
|
||
|
|
|
||
|
|
// Числа — по ВСЕМ событиям окна, а не по показанной сотне.
|
||
|
|
expect($response->json('totals.sent'))->toBe(105)
|
||
|
|
->and($response->json('totals.skipped'))->toBe(1)
|
||
|
|
->and($response->json('messages'))->toHaveCount(100)
|
||
|
|
// Человеку надо сказать, что список урезан, иначе он решит, что видит всё (В-48).
|
||
|
|
->and($response->json('messages_limit'))->toBe(100)
|
||
|
|
->and($response->json('messages_truncated'))->toBeTrue();
|
||
|
|
});
|