Files
portal/app/tests/Feature/Sales/SalesSmsScreenTest.php
T

128 lines
5.2 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Models\SalesAdAudienceFirm;
use App\Models\SalesAdAudiencePhone;
use App\Models\SalesUser;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\Concerns\SharesSupplierPdo;
/**
* Раздел «Прогрев СМС» в кабинете начальника (Task 9, бэк).
*
* GET /api/sales/sms/campaigns — список рассылок + песочница + отправители
* POST /api/sales/sms/campaigns — запуск рассылки
* GET /api/sales/sms/campaigns/{id} — карточка с журналом
* POST /api/sales/sms/preview — цена и причины пропуска ДО отправки
*
* Все методы — ТОЛЬКО начальник (role=head), как у SalesAdAudienceController.
* Менеджер → 403. Аутентификация: $this->actingAs($user, 'sales').
*
* SharesSupplierPdo обязателен: модели sales_sms_* прибиты к pgsql_supplier
* (отдельный PDO), без шаринга DatabaseTransactions откатывал бы только
* соединение по умолчанию — записи текли бы из теста в тест (правило 10 плана).
*/
uses(DatabaseTransactions::class, SharesSupplierPdo::class);
// ── helpers (уникальный префикс sms_, чтобы не столкнуться с ads_* соседнего файла) ──
//
// SalesUser::factory() в проекте НЕТ (правило 11 плана) — заводим через create()
// с uniqid(), как в AdAudienceScreenTest.php.
function sms_head(): SalesUser
{
return SalesUser::create([
'name' => 'Начальник '.uniqid(),
'email' => 'smshead'.uniqid().'@sales.local',
'password' => bcrypt('secret'),
'role' => 'head',
'is_active' => true,
]);
}
function sms_manager(): SalesUser
{
return SalesUser::create([
'name' => 'Менеджер '.uniqid(),
'email' => 'smsmgr'.uniqid().'@sales.local',
'password' => bcrypt('secret'),
'role' => 'manager',
'is_active' => true,
]);
}
it('менеджеру раздел закрыт', function () {
$manager = sms_manager();
$this->actingAs($manager, 'sales')
->getJson('/api/sales/sms/campaigns')
->assertStatus(403);
});
it('начальник видит список рассылок', function () {
$head = sms_head();
$this->actingAs($head, 'sales')
->getJson('/api/sales/sms/campaigns')
->assertOk()
->assertJsonStructure(['campaigns', 'sandbox']);
});
it('предпросмотр показывает цену и причины пропуска до отправки', function () {
$head = sms_head();
$this->actingAs($head, 'sales')
->postJson('/api/sales/sms/preview', [
'body' => str_repeat('а', 80),
'phones' => ['79990000001'],
])
->assertOk()
->assertJsonStructure(['segments', 'sendable_count', 'skipped', 'estimated_cost_kopecks'])
->assertJsonPath('segments', 2);
});
it('не создаёт рассылку с пустым текстом', function () {
$head = sms_head();
$this->actingAs($head, 'sales')
->postJson('/api/sales/sms/campaigns', ['title' => 'Пустая', 'body' => '', 'phones' => ['79990000001']])
->assertStatus(422);
});
// ── Task 10.5: номера отмеченных фирм ─────────────────────────────────────────
//
// Начальник отмечает фирмы галочками, а не переписывает 177 номеров руками.
// POST /api/sales/sms/recipients — по списку id фирм отдаёт их живые номера.
it('отдаёт номера отмеченных фирм', function () {
$firm = SalesAdAudienceFirm::create(['firm_name' => 'ООО Ромашка', 'firm_inn' => '2452108518']);
SalesAdAudiencePhone::create([
'phone' => '79990000010', 'firm_id' => $firm->id, 'firm_name' => 'ООО Ромашка',
'expires_at' => now()->addDays(30), 'state' => 'active',
'operator' => 'МТС', 'phone_type' => 'mobile',
]);
$this->actingAs(sms_head(), 'sales')
->postJson('/api/sales/sms/recipients', ['firm_ids' => [$firm->id]])
->assertOk()
->assertJsonPath('phones.0.phone', '79990000010')
->assertJsonPath('phones.0.firm_name', 'ООО Ромашка');
});
it('не отдаёт погашенные номера', function () {
$firm = SalesAdAudienceFirm::create(['firm_name' => 'ООО Ушедшая', 'firm_inn' => '2452108519']);
SalesAdAudiencePhone::create([
'phone' => '79990000011', 'firm_id' => $firm->id,
'expires_at' => now()->addDays(30), 'state' => 'active',
'removed_at' => now(), 'operator' => 'МТС', 'phone_type' => 'mobile',
]);
$this->actingAs(sms_head(), 'sales')
->postJson('/api/sales/sms/recipients', ['firm_ids' => [$firm->id]])
->assertOk()
->assertJsonPath('phones', []);
});