Files
portal/app/tests/Feature/SetTenantContextTest.php
T

84 lines
3.3 KiB
PHP
Raw Normal View History

<?php
declare(strict_types=1);
use App\Http\Middleware\SetTenantContext;
use App\Models\Project;
use App\Models\Tenant;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Illuminate\Support\Facades\Route;
/**
* Тесты middleware SetTenantContext: резолюция tenant_id и установка
* `app.current_tenant_id` для RLS-фильтрации.
*
* Не проверяет RLS-фильтрацию в самом запросе — это RlsSmokeTest.
* Здесь только что middleware корректно устанавливает PG-переменную.
*/
uses(DatabaseTransactions::class);
beforeEach(function () {
Route::middleware([SetTenantContext::class])->get('/_test/tenant-context', function () {
// current_setting вернёт NULL если не установлено (с missing_ok=true).
$value = DB::selectOne("SELECT current_setting('app.current_tenant_id', true) AS v")->v;
return response()->json(['tenant_id' => $value]);
});
});
test('middleware возвращает 403 без tenant context', function () {
$response = $this->get('/_test/tenant-context');
$response->assertStatus(403);
});
test('middleware устанавливает tenant_id из X-Tenant-Id header', function () {
$tenant = Tenant::factory()->create();
$response = $this->withHeaders(['X-Tenant-Id' => (string) $tenant->id])
->get('/_test/tenant-context');
$response->assertStatus(200);
expect((int) $response->json('tenant_id'))->toBe($tenant->id);
});
test('middleware игнорирует не-числовой X-Tenant-Id header', function () {
$response = $this->withHeaders(['X-Tenant-Id' => 'not-a-number'])
->get('/_test/tenant-context');
$response->assertStatus(403);
});
test('middleware резолвит tenant_id по subdomain', function () {
$tenant = Tenant::factory()->create(['subdomain' => 'acme-corp']);
$response = $this->get('http://acme-corp.liderra.ru/_test/tenant-context');
$response->assertStatus(200);
expect((int) $response->json('tenant_id'))->toBe($tenant->id);
});
test('middleware с tenant_id корректно фильтрует данные через RLS', function () {
// Этот тест НЕ работает на postgres-superuser (BYPASSRLS),
// но показывает что middleware устанавливает контекст для будущих
// запросов под crm_app_user. Проверка эквивалентности значения.
$tenant1 = Tenant::factory()->create();
$tenant2 = Tenant::factory()->create();
Project::factory()->count(2)->create(['tenant_id' => $tenant1->id]);
Project::factory()->count(3)->create(['tenant_id' => $tenant2->id]);
Route::middleware([SetTenantContext::class])->get('/_test/projects-count', function () {
// Без BYPASSRLS было бы COUNT только tenant1's projects
$tenant1Count = DB::table('projects')->where('tenant_id', request()->header('X-Expected-Tenant'))->count();
return response()->json(['count' => $tenant1Count]);
});
$response = $this->withHeaders([
'X-Tenant-Id' => (string) $tenant1->id,
'X-Expected-Tenant' => (string) $tenant1->id,
])->get('/_test/projects-count');
$response->assertStatus(200);
expect($response->json('count'))->toBe(2);
});