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', []); });