actingAs($user, 'sales'). * Изоляция: DatabaseTransactions. Глобально подключён SharesAdminPdo (Pest.php). * DB_DATABASE=liderra_testing ОБЯЗАТЕЛЕН при запуске. */ uses(DatabaseTransactions::class); // ── helpers (уникальный префикс mgr_) ──────────────────────────────────────── function mgr_head(): SalesUser { return SalesUser::create([ 'name' => 'Начальник '.uniqid(), 'email' => 'mgrhead'.uniqid().'@sales.local', 'password' => bcrypt('secret'), 'role' => 'head', 'is_active' => true, ]); } function mgr_manager(): SalesUser { return SalesUser::create([ 'name' => 'Менеджер '.uniqid(), 'email' => 'mgrmgr'.uniqid().'@sales.local', 'password' => bcrypt('secret'), 'role' => 'manager', 'is_active' => true, ]); } function mgr_tenant(): Tenant { return Tenant::factory()->create([ 'balance_rub' => 0.0, 'delivered_in_month' => 0, ]); } function mgr_assignment(SalesUser $user, Tenant $tenant): SalesClientAssignment { return SalesClientAssignment::create([ 'sales_user_id' => $user->id, 'tenant_id' => $tenant->id, 'tariff_kind' => 'topup_step', 'tariff_params' => ['threshold' => 0, 'reward' => 0, 'periods' => [['from' => 1, 'to' => 999, 'rate' => 20]]], 'assigned_at' => Carbon::now('Europe/Moscow'), ]); } function mgr_payout(int $managerId, int $headId, string $amount): SalesPayout { return SalesPayout::create([ 'sales_user_id' => $managerId, 'amount_rub' => $amount, 'paid_on' => Carbon::now('Europe/Moscow')->format('Y-m-d'), 'comment' => null, 'created_by' => $headId, ]); } // ── 1. доступ: только head ──────────────────────────────────────────────────── test('менеджер POST /api/sales/managers → 403', function () { $manager = mgr_manager(); $this->actingAs($manager, 'sales')->postJson('/api/sales/managers', [ 'name' => 'Новый', 'email' => 'new'.uniqid().'@sales.local', 'password' => 'secret123', 'role' => 'manager', ])->assertStatus(403) ->assertJson(['message' => 'Доступно только начальнику отдела.']); }); test('менеджер GET /api/sales/managers → 403', function () { $manager = mgr_manager(); $this->actingAs($manager, 'sales')->getJson('/api/sales/managers') ->assertStatus(403) ->assertJson(['message' => 'Доступно только начальнику отдела.']); }); // ── 2. store: happy path ────────────────────────────────────────────────────── test('head создаёт менеджера → 201, пароль захеширован, без password в ответе', function () { $head = mgr_head(); $email = 'created'.uniqid().'@sales.local'; $response = $this->actingAs($head, 'sales')->postJson('/api/sales/managers', [ 'name' => 'Иван Петров', 'email' => $email, 'password' => 'plainSecret6', 'role' => 'manager', ]); $response->assertCreated() ->assertJsonPath('name', 'Иван Петров') ->assertJsonPath('email', $email) ->assertJsonPath('role', 'manager') ->assertJsonPath('is_active', true); // Ответ НЕ содержит пароль. expect($response->json())->not->toHaveKey('password'); $this->assertDatabaseHas('sales_users', [ 'email' => $email, 'role' => 'manager', 'created_by' => $head->id, ]); // Пароль в БД захеширован (НЕ равен исходному) и проходит Hash::check. $created = SalesUser::where('email', $email)->firstOrFail(); expect($created->password)->not->toBe('plainSecret6') ->and(Hash::check('plainSecret6', $created->password))->toBeTrue(); }); // ── 3. store: валидация ────────────────────────────────────────────────────── test('store: дубль email → 422', function () { $head = mgr_head(); $existing = mgr_manager(); $this->actingAs($head, 'sales')->postJson('/api/sales/managers', [ 'name' => 'Дубль', 'email' => $existing->email, 'password' => 'secret123', 'role' => 'manager', ])->assertStatus(422); }); test('store: невалидный role → 422', function () { $head = mgr_head(); $this->actingAs($head, 'sales')->postJson('/api/sales/managers', [ 'name' => 'Кто-то', 'email' => 'role'.uniqid().'@sales.local', 'password' => 'secret123', 'role' => 'superadmin', ])->assertStatus(422); }); test('store: короткий пароль → 422', function () { $head = mgr_head(); $this->actingAs($head, 'sales')->postJson('/api/sales/managers', [ 'name' => 'Кто-то', 'email' => 'short'.uniqid().'@sales.local', 'password' => '123', 'role' => 'manager', ])->assertStatus(422); }); // ── 4. index: список с метриками ────────────────────────────────────────────── test('index (head): список с clients_count и paid_all_time_rub', function () { $head = mgr_head(); $manager = mgr_manager(); // 2 привязки + 1 выплата у менеджера. mgr_assignment($manager, mgr_tenant()); mgr_assignment($manager, mgr_tenant()); mgr_payout($manager->id, $head->id, '7500.00'); $response = $this->actingAs($head, 'sales')->getJson('/api/sales/managers'); $response->assertOk(); $data = collect($response->json('data')); $row = $data->firstWhere('id', $manager->id); expect($row)->not->toBeNull() ->and($row['clients_count'])->toBe(2) ->and((float) $row['paid_all_time_rub'])->toBe(7500.0) ->and($row)->toHaveKeys([ 'id', 'name', 'email', 'role', 'is_active', 'clients_count', 'paid_all_time_rub', ]); // head тоже в списке (index возвращает всех sales_users). $headRow = $data->firstWhere('id', $head->id); expect($headRow)->not->toBeNull() ->and($headRow['role'])->toBe('head') ->and($headRow['clients_count'])->toBe(0) ->and((float) $headRow['paid_all_time_rub'])->toBe(0.0); });