Files
portal/app/tests/Feature/Sales/SalesManagerAccountTest.php
T
Дмитрий fb9e5b581d feat(sales): создание менеджеров начальником + список (бэк)
Task 7.1a — SalesAccountService + store/index в SalesManagersController,
только head.
- store: создаёт sales_user (пароль Hash::make, created_by=head,
  base_salary=0); email unique → 422; role in manager|head; password min:6;
  в ответе нет password ($hidden).
- index: список всех sales_users (head-первыми) с clients_count и
  выплачено всего.
Менеджер не может создавать/смотреть список (403).

Тесты 7/7 (вкл. проверку хеша пароля), sales-набор 174/174, Larastan 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:34:47 +03:00

206 lines
7.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
declare(strict_types=1);
use App\Models\SalesClientAssignment;
use App\Models\SalesPayout;
use App\Models\SalesUser;
use App\Models\Tenant;
use Carbon\Carbon;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Hash;
/**
* TDD: SalesManagersController::store/index + SalesAccountService — заведение
* менеджеров начальником и список аккаунтов отдела (Task 7.1a, бэк).
*
* POST /api/sales/managers → head создаёт менеджера/начальника (201)
* GET /api/sales/managers → head видит список всех sales_users с метриками
*
* Оба метода — ТОЛЬКО начальник (role=head). Менеджер → 403.
*
* Аутентификация: $this->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' => 'percent_oborot',
'tariff_params' => ['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);
});