actingAs($user, 'sales'). * * SharesSupplierPdo обязателен: SalesAdAudiencePhone/State прибиты к pgsql_supplier * (отдельный PDO), без шаринга DatabaseTransactions откатывал бы только соединение * по умолчанию — записи текли бы из теста в тест. */ uses(DatabaseTransactions::class, SharesSupplierPdo::class); // ── helpers (уникальный префикс ads_) ──────────────────────────────────────── function ads_head(): SalesUser { return SalesUser::create([ 'name' => 'Начальник '.uniqid(), 'email' => 'adshead'.uniqid().'@sales.local', 'password' => bcrypt('secret'), 'role' => 'head', 'is_active' => true, ]); } function ads_manager(): SalesUser { return SalesUser::create([ 'name' => 'Менеджер '.uniqid(), 'email' => 'adsmgr'.uniqid().'@sales.local', 'password' => bcrypt('secret'), 'role' => 'manager', 'is_active' => true, ]); } /** Номер в аудитории: живой, просроченный или не отправленный — по аргументам. */ function ads_phone(string $phone, array $attrs = []): SalesAdAudiencePhone { return SalesAdAudiencePhone::create(array_merge([ 'phone' => $phone, 'added_at' => now(), 'expires_at' => now()->addDays(30), ], $attrs)); } // ── доступ ──────────────────────────────────────────────────────────────────── test('менеджер не видит экран рекламы', function () { $this->actingAs(ads_manager(), 'sales') ->getJson('/api/sales/ad-audience') ->assertForbidden(); }); test('менеджер не может тронуть настройки рекламы', function () { SalesAdAudienceState::current()->update(['enabled' => false, 'days' => 30]); $this->actingAs(ads_manager(), 'sales') ->patchJson('/api/sales/ad-audience', ['enabled' => true]) ->assertForbidden(); expect(SalesAdAudienceState::current()->fresh()->enabled)->toBeFalse(); }); test('без входа экран закрыт', function () { $this->getJson('/api/sales/ad-audience')->assertUnauthorized(); }); // ── состояние ───────────────────────────────────────────────────────────────── test('начальник видит три числа по номерам', function () { SalesAdAudienceState::current()->update([ 'enabled' => true, 'days' => 30, 'yandex_segment_id' => 777, 'last_error' => null, ]); // В рекламе сейчас: живые (не удалённые, срок не вышел) — 3 штуки. ads_phone('79990000009', ['synced_at' => now(), 'expires_at' => now()->addDays(30)]); ads_phone('79990000009', ['synced_at' => now(), 'expires_at' => now()->addDays(3)]); // выходит на неделе ads_phone('79990000009', ['expires_at' => now()->addDays(20)]); // ждёт отправки // Просроченный и удалённый в счёт не идут. ads_phone('79990000009', ['synced_at' => now(), 'expires_at' => now()->subDay()]); ads_phone('79990000009', ['synced_at' => now(), 'removed_at' => now()]); $response = $this->actingAs(ads_head(), 'sales') ->getJson('/api/sales/ad-audience') ->assertOk(); $response->assertJsonPath('data.in_ads', 3) ->assertJsonPath('data.expiring_week', 1) ->assertJsonPath('data.waiting_sync', 1) ->assertJsonPath('data.enabled', true) ->assertJsonPath('data.days', 30) ->assertJsonPath('data.segment_id', 777); }); test('пустая аудитория отдаёт нули, а не ошибку', function () { $this->actingAs(ads_head(), 'sales') ->getJson('/api/sales/ad-audience') ->assertOk() ->assertJsonPath('data.in_ads', 0) ->assertJsonPath('data.expiring_week', 0) ->assertJsonPath('data.waiting_sync', 0); }); test('ошибка последней синхронизации видна начальнику', function () { SalesAdAudienceState::current()->update(['last_error' => 'не задан YANDEX_AUDIENCE_TOKEN']); $this->actingAs(ads_head(), 'sales') ->getJson('/api/sales/ad-audience') ->assertOk() ->assertJsonPath('data.last_error', 'не задан YANDEX_AUDIENCE_TOKEN'); }); // ── настройки ───────────────────────────────────────────────────────────────── test('начальник включает рекламу и меняет срок', function () { SalesAdAudienceState::current()->update(['enabled' => false, 'days' => 30]); $this->actingAs(ads_head(), 'sales') ->patchJson('/api/sales/ad-audience', ['enabled' => true, 'days' => 45]) ->assertOk() ->assertJsonPath('data.enabled', true) ->assertJsonPath('data.days', 45); $state = SalesAdAudienceState::current()->fresh(); expect($state->enabled)->toBeTrue()->and($state->days)->toBe(45); }); test('можно менять только рубильник, срок остаётся прежним', function () { SalesAdAudienceState::current()->update(['enabled' => true, 'days' => 15]); $this->actingAs(ads_head(), 'sales') ->patchJson('/api/sales/ad-audience', ['enabled' => false]) ->assertOk() ->assertJsonPath('data.enabled', false) ->assertJsonPath('data.days', 15); }); test('бессмысленный срок отвергается', function () { SalesAdAudienceState::current()->update(['days' => 30]); foreach ([0, -5, 400, 'много'] as $bad) { $this->actingAs(ads_head(), 'sales') ->patchJson('/api/sales/ad-audience', ['days' => $bad]) ->assertStatus(422); } expect(SalesAdAudienceState::current()->fresh()->days)->toBe(30); });