Preserva base local aceite de identidade e acessos
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Modules\Identity\Models\Company;
|
||||
use App\Modules\Identity\Models\Tenant;
|
||||
use App\Modules\Identity\Models\Unit;
|
||||
use App\Shared\Context\CompanyContext;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class CompanyIsolationTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private function scenario(): array
|
||||
{
|
||||
$tenant = Tenant::create(['name' => 'Tenant A']);
|
||||
$otherTenant = Tenant::create(['name' => 'Tenant B']);
|
||||
$company = Company::create(['tenant_id' => $tenant->id, 'name' => 'Company A']);
|
||||
$sibling = Company::create(['tenant_id' => $tenant->id, 'name' => 'Company B']);
|
||||
$foreign = Company::create(['tenant_id' => $otherTenant->id, 'name' => 'Company C']);
|
||||
$user = User::create(['name' => 'Synthetic User', 'email' => 'synthetic@example.test', 'password' => 'test-only-password']);
|
||||
DB::table('company_memberships')->insert(['user_id' => $user->id, 'tenant_id' => $tenant->id, 'company_id' => $company->id]);
|
||||
\Tests\Support\IdentityFixture::grant($user->id, $tenant->id, $company->id, 'company.view');
|
||||
|
||||
return [$user, $tenant, $company, $sibling, $foreign];
|
||||
}
|
||||
|
||||
public function test_membership_is_required_even_for_another_company_in_the_same_tenant(): void
|
||||
{
|
||||
[$user, $tenant, $company, $sibling, $foreign] = $this->scenario();
|
||||
$this->actingAs($user)->withSession(['auth_version' => 1, 'company_context' => ['tenant_id' => $tenant->id, 'company_id' => $company->id, 'unit_id' => null, 'token' => 'synthetic']])
|
||||
->getJson('/api/context')->assertOk()->assertExactJson(['tenant_id' => $tenant->id, 'company_id' => $company->id, 'unit_id' => null]);
|
||||
foreach ([$sibling, $foreign] as $denied) {
|
||||
$this->withSession(['company_context' => ['tenant_id' => $denied->tenant_id, 'company_id' => $denied->id, 'unit_id' => null, 'token' => 'synthetic']])->getJson('/api/context')->assertForbidden();
|
||||
}
|
||||
$this->withSession(['company_context' => []])->getJson('/api/context')->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_company_owned_reads_and_bulk_writes_do_not_cross_company_boundaries(): void
|
||||
{
|
||||
[$user, $tenant, $company, $sibling] = $this->scenario();
|
||||
DB::table('units')->insert([
|
||||
['tenant_id' => $tenant->id, 'company_id' => $company->id, 'name' => 'Visible'],
|
||||
['tenant_id' => $tenant->id, 'company_id' => $sibling->id, 'name' => 'Private'],
|
||||
]);
|
||||
$visibleId = DB::table('units')->where('company_id', $company->id)->value('id');
|
||||
\Tests\Support\IdentityFixture::grant($user->id, $tenant->id, $company->id, 'unit.view', $visibleId);
|
||||
$context = app(CompanyContext::class);
|
||||
$context->selectFor($user->id, $tenant->id, $company->id);
|
||||
$this->assertSame(['Visible'], Unit::query()->pluck('name')->all());
|
||||
$this->assertSame(1, Unit::query()->update(['name' => 'Updated']));
|
||||
$this->assertSame('Private', DB::table('units')->where('company_id', $sibling->id)->value('name'));
|
||||
$unit = Unit::create(['name' => 'New']);
|
||||
$this->assertSame($company->id, $unit->company_id);
|
||||
$context->clear();
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
Unit::query()->count();
|
||||
}
|
||||
|
||||
public function test_stale_model_cannot_be_saved_under_another_company_context(): void
|
||||
{
|
||||
[$user, $tenant, $company, $sibling] = $this->scenario();
|
||||
DB::table('company_memberships')->insert(['user_id' => $user->id, 'tenant_id' => $tenant->id, 'company_id' => $sibling->id]);
|
||||
\Tests\Support\IdentityFixture::grant($user->id, $tenant->id, $sibling->id, 'company.view');
|
||||
$context = app(CompanyContext::class);
|
||||
$context->selectFor($user->id, $tenant->id, $company->id);
|
||||
$unit = Unit::create(['name' => 'Original']);
|
||||
$context->selectFor($user->id, $tenant->id, $sibling->id);
|
||||
$unit->name = 'Forbidden';
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
$unit->save();
|
||||
}
|
||||
|
||||
public function test_database_rejects_mismatched_tenant_company_relationship(): void
|
||||
{
|
||||
[, $tenant, , , $foreign] = $this->scenario();
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
DB::table('units')->insert(['tenant_id' => $tenant->id, 'company_id' => $foreign->id, 'name' => 'Invalid']);
|
||||
}
|
||||
|
||||
public function test_context_is_cleared_after_a_request(): void
|
||||
{
|
||||
[$user, $tenant, $company] = $this->scenario();
|
||||
$this->actingAs($user)->withSession(['auth_version' => 1, 'company_context' => ['tenant_id' => $tenant->id, 'company_id' => $company->id, 'unit_id' => null, 'token' => 'synthetic']])->getJson('/api/context')->assertOk();
|
||||
$this->expectException(AccessDeniedHttpException::class);
|
||||
app(CompanyContext::class)->identifiers();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user