Files
portal/app/tests/Feature/LookupsTest.php
T
Дмитрий 2a34ee880a fix(security): закрыть открытые эндпоинты + SSRF-гард webhook перед go-live
- /api/dashboard/summary, /api/managers, /api/lead-statuses: были без auth
  (tenant_id параметром) → auth:sanctum (+tenant); tenant_id из authed-user,
  не из параметра — закрывает кросс-tenant утечку KPI/списка пользователей
- ManagerController: явный where(tenant_id) поверх RLS (BYPASSRLS-роли/тесты)
- WebhookUrlGuard + webhooks/test: SSRF-блок private/reserved/loopback IP
  (cloud-metadata 169.254.169.254 и пр.); update()/delivery — follow-up
- TDD: +EndpointAuthHardeningTest(5) +WebhookSsrfGuardTest(10); обновлены
  Dashboard/Lookups/LeadStatuses тесты под auth
- регрессия tests/Feature 960/964 (2 фейла pre-existing: Vite-manifest env +
  RouteSupplierLeadJobBilling idempotency — оба фейлят и на чистом base)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-21 19:15:05 +03:00

101 lines
3.8 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\Tenant;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
uses(RefreshDatabase::class);
beforeEach(function () {
$this->tenant = Tenant::factory()->create();
});
test('GET /api/managers возвращает active users тенанта', function () {
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
// actingAs одного из активных пользователей тенанта — он сам входит в список.
$ivan = User::factory()->for($this->tenant)->create([
'first_name' => 'Иван', 'last_name' => 'Петров', 'is_active' => true,
]);
User::factory()->for($this->tenant)->create([
'first_name' => 'Ольга', 'last_name' => 'Романова', 'is_active' => true,
]);
User::factory()->for($this->tenant)->create([
'first_name' => 'Удалённый', 'is_active' => false,
]);
$this->actingAs($ivan);
$r = $this->getJson('/api/managers');
$r->assertStatus(200);
$managers = $r->json('managers');
expect($managers)->toHaveCount(2);
$names = array_column($managers, 'name');
expect($names)->toContain('Иван П.')->toContain('Ольга Р.');
});
test('GET /api/managers возвращает initials с fallback на email', function () {
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$admin = User::factory()->for($this->tenant)->create([
'email' => 'admin@example.ru',
'first_name' => null,
'last_name' => null,
'is_active' => true,
]);
$this->actingAs($admin);
$r = $this->getJson('/api/managers');
$r->assertStatus(200);
$manager = $r->json('managers.0');
expect($manager['name'])->toBe('admin@example.ru');
expect($manager['initials'])->toBe('AD');
});
test('GET /api/managers без авторизации возвращает 401', function () {
$this->getJson('/api/managers')->assertStatus(401);
});
test('POST /api/deals 422 если manager_id не принадлежит tenant\'у', function () {
$otherTenant = Tenant::factory()->create();
DB::statement('SET app.current_tenant_id = '.$otherTenant->id);
$otherManager = User::factory()->for($otherTenant)->create(['is_active' => true]);
// Назначаем чужого менеджера на свою сделку — должен быть 422.
$this->actingAs(User::factory()->for($this->tenant)->create());
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
'manager_id' => $otherManager->id,
]);
$r->assertStatus(422);
expect($r->json('errors'))->toHaveKey('manager_id');
});
test('POST /api/deals 422 если manager_id не активен (is_active=false)', function () {
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$inactive = User::factory()->for($this->tenant)->create(['is_active' => false]);
$this->actingAs(User::factory()->for($this->tenant)->create());
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
'manager_id' => $inactive->id,
]);
$r->assertStatus(422);
});
test('POST /api/deals принимает manager_id из своего tenant\'а', function () {
DB::statement('SET app.current_tenant_id = '.$this->tenant->id);
$manager = User::factory()->for($this->tenant)->create(['is_active' => true]);
$this->actingAs(User::factory()->for($this->tenant)->create());
$r = $this->postJson('/api/deals', [
'project_name' => 'X',
'phone' => '+7 (999) 000-00-00',
'manager_id' => $manager->id,
]);
$r->assertStatus(201);
expect($r->json('deal.manager_id'))->toBe($manager->id);
});