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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Shared\Testing\TestingDatabaseGuard;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use RuntimeException;
|
||||
use Tests\TestCase;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
final class DatabaseSafetyTest extends TestCase
|
||||
{
|
||||
public function test_test_runner_has_no_cluster_administration_privileges(): void
|
||||
{
|
||||
$role = DB::selectOne('SELECT rolsuper, rolcreatedb, rolcreaterole, rolbypassrls FROM pg_roles WHERE rolname = current_user');
|
||||
$this->assertFalse($role->rolsuper);
|
||||
$this->assertFalse($role->rolcreatedb);
|
||||
$this->assertFalse($role->rolcreaterole);
|
||||
$this->assertFalse($role->rolbypassrls);
|
||||
$this->assertFalse(DB::selectOne("SELECT has_database_privilege(current_user, 'sgf_tempo_dev', 'CONNECT') AS allowed")->allowed);
|
||||
}
|
||||
|
||||
public function test_guard_rejects_development_before_connecting(): void
|
||||
{
|
||||
config(['database.connections.unsafe' => array_merge(config('database.connections.pgsql'), ['database' => 'sgf_tempo_dev'])]);
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('SGF_TEST_DATABASE_REFUSED');
|
||||
TestingDatabaseGuard::assertSafe('unsafe');
|
||||
}
|
||||
|
||||
public function test_guard_rejects_connection_url_overrides(): void
|
||||
{
|
||||
config(['database.connections.unsafe' => array_merge(config('database.connections.pgsql'), ['url' => 'pgsql://invalid'])]);
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('SGF_TEST_DATABASE_REFUSED');
|
||||
TestingDatabaseGuard::assertSafe('unsafe');
|
||||
}
|
||||
|
||||
public function test_destructive_cli_command_refuses_the_development_environment(): void
|
||||
{
|
||||
$process = new Process([PHP_BINARY, 'artisan', 'migrate:fresh', '--env=local', '--no-interaction'], base_path(), [
|
||||
'APP_ENV' => 'local', 'DB_CONNECTION' => false, 'DB_HOST' => false,
|
||||
'DB_PORT' => false, 'DB_DATABASE' => false, 'DB_USERNAME' => false,
|
||||
'DB_PASSWORD' => false, 'DB_URL' => false,
|
||||
]);
|
||||
$process->run();
|
||||
$this->assertFalse($process->isSuccessful());
|
||||
$this->assertStringContainsString('SGF_TEST_DATABASE_REFUSED', $process->getOutput().$process->getErrorOutput());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
// use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*/
|
||||
public function test_the_application_returns_a_successful_response(): void
|
||||
{
|
||||
$response = $this->get('/');
|
||||
|
||||
$response->assertStatus(200);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Tests\Support\IdentityFixture;
|
||||
use Tests\TestCase;
|
||||
|
||||
final class IdentityWorkflowTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
private array $fixture;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->fixture = IdentityFixture::create();
|
||||
}
|
||||
|
||||
private function login(string $kind = 'manager'): void
|
||||
{
|
||||
$this->postJson('/api/login', ['email' => $this->fixture[$kind]->email, 'password' => $this->fixture['password']])->assertOk();
|
||||
}
|
||||
|
||||
private function select(?int $company = null, ?int $unit = null): string
|
||||
{
|
||||
return $this->postJson('/api/context', ['tenant_id' => $this->fixture['tenant'], 'company_id' => $company ?? $this->fixture['company'], 'unit_id' => $unit])
|
||||
->assertOk()->json('context.token');
|
||||
}
|
||||
|
||||
public function test_real_credential_login_invalid_password_and_logout(): void
|
||||
{
|
||||
$this->postJson('/api/login', ['email' => $this->fixture['manager']->email, 'password' => 'incorrect'])->assertUnprocessable();
|
||||
$this->login();
|
||||
$this->select();
|
||||
$this->getJson('/api/context')->assertOk();
|
||||
$this->postJson('/api/logout')->assertOk();
|
||||
$this->getJson('/api/context')->assertUnauthorized();
|
||||
$this->getJson('/api/session')->assertJsonPath('user', null);
|
||||
}
|
||||
|
||||
public function test_login_rate_limit(): void
|
||||
{
|
||||
for ($i = 0; $i < 5; $i++) { $this->postJson('/api/login', ['email' => $this->fixture['reader']->email, 'password' => 'incorrect'])->assertUnprocessable(); }
|
||||
$this->postJson('/api/login', ['email' => $this->fixture['reader']->email, 'password' => 'incorrect'])->assertStatus(429);
|
||||
}
|
||||
|
||||
public function test_session_id_rotates_on_login_and_logout(): void
|
||||
{
|
||||
$this->getJson('/api/session'); $before = session()->getId();
|
||||
$this->login(); $loggedIn = session()->getId();
|
||||
$this->assertNotSame($before, $loggedIn);
|
||||
$this->postJson('/api/logout')->assertOk();
|
||||
$this->assertNotSame($loggedIn, session()->getId());
|
||||
}
|
||||
|
||||
public function test_reader_cannot_view_other_company_or_hidden_unit_or_write(): void
|
||||
{
|
||||
$this->login('reader');
|
||||
$companies = $this->getJson('/api/session')->assertOk()->json('companies');
|
||||
$this->assertCount(1, $companies);
|
||||
$this->postJson('/api/context', ['tenant_id' => $this->fixture['tenant'], 'company_id' => $this->fixture['sibling']])->assertForbidden();
|
||||
$this->postJson('/api/context', ['tenant_id' => $this->fixture['otherTenant'], 'company_id' => $this->fixture['foreign']])->assertForbidden();
|
||||
$this->postJson('/api/context', ['tenant_id' => $this->fixture['tenant'], 'company_id' => $this->fixture['company'], 'unit_id' => $this->fixture['hidden']])->assertForbidden();
|
||||
$token = $this->select(unit: $this->fixture['unit']);
|
||||
$this->getJson('/api/companies/'.$this->fixture['company'])->assertOk();
|
||||
$this->getJson('/api/companies/'.$this->fixture['sibling'])->assertForbidden();
|
||||
$this->getJson('/api/units/'.$this->fixture['unit'])->assertOk();
|
||||
$this->getJson('/api/units/'.$this->fixture['hidden'])->assertForbidden();
|
||||
$this->assertCount(1, $this->getJson('/api/session')->json('context.units'));
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/companies/'.$this->fixture['company'], ['name' => 'Forbidden'])->assertForbidden();
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/units/'.$this->fixture['unit'], ['name' => 'Forbidden'])->assertForbidden();
|
||||
$this->withHeader('X-SGF-Context', $token)->postJson('/api/units', ['name' => 'Forbidden'])->assertForbidden();
|
||||
$this->getJson('/api/accesses')->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_authorized_rename_creation_and_audit(): void
|
||||
{
|
||||
$this->login(); $token = $this->select(unit: $this->fixture['unit']);
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/companies/'.$this->fixture['company'], ['name' => 'Empresa actualizada'])->assertOk();
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/units/'.$this->fixture['unit'], ['name' => 'Unidade actualizada'])->assertOk();
|
||||
$new = $this->withHeader('X-SGF-Context', $token)->postJson('/api/units', ['name' => 'Nova unidade'])->assertCreated()->json('id');
|
||||
$this->assertDatabaseHas('permission_grants', ['user_id' => $this->fixture['manager']->id, 'unit_id' => $new, 'permission' => 'unit.view']);
|
||||
$this->assertDatabaseHas('permission_grants', ['user_id' => $this->fixture['manager']->id, 'unit_id' => $new, 'permission' => 'unit.edit']);
|
||||
$this->assertDatabaseHas('identity_audit', ['action' => 'company.renamed']);
|
||||
$this->assertDatabaseHas('identity_audit', ['action' => 'access.granted_on_creation', 'unit_id' => $new]);
|
||||
$this->assertStringNotContainsString($this->fixture['password'], json_encode(DB::table('identity_audit')->get()));
|
||||
}
|
||||
|
||||
public function test_old_form_token_is_rejected_after_switching_company_or_unit(): void
|
||||
{
|
||||
$this->login(); $old = $this->select(unit: $this->fixture['unit']);
|
||||
$this->select($this->fixture['sibling']);
|
||||
$this->getJson('/api/session')->assertJsonPath('context.unit', null);
|
||||
$this->withHeader('X-SGF-Context', $old)->patchJson('/api/companies/'.$this->fixture['company'], ['name' => 'Stale'])->assertStatus(409);
|
||||
$fresh = $this->select(unit: $this->fixture['unit']);
|
||||
$this->select();
|
||||
$this->withHeader('X-SGF-Context', $fresh)->patchJson('/api/units/'.$this->fixture['unit'], ['name' => 'Stale'])->assertStatus(409);
|
||||
}
|
||||
|
||||
public function test_manipulated_identifier_and_payload_are_rejected(): void
|
||||
{
|
||||
$this->login(); $token = $this->select(unit: $this->fixture['unit']);
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/companies/'.$this->fixture['sibling'], ['name' => 'Cross'])->assertForbidden();
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/units/'.$this->fixture['hidden'], ['name' => 'Cross'])->assertStatus(409);
|
||||
$this->withHeader('X-SGF-Context', $token)->patchJson('/api/units/'.$this->fixture['unit'], ['name' => 'Cross', 'company_id' => $this->fixture['sibling']])->assertUnprocessable();
|
||||
}
|
||||
|
||||
public function test_manager_cannot_delegate_rights_they_do_not_have(): void
|
||||
{
|
||||
$this->login('limited'); $token = $this->select(unit: $this->fixture['unit']);
|
||||
$this->withHeader('X-SGF-Context', $token)->postJson('/api/accesses', ['email' => $this->fixture['reader']->email, 'permission' => 'company.edit', 'reason' => 'Synthetic test'])->assertForbidden();
|
||||
$this->withHeader('X-SGF-Context', $token)->postJson('/api/accesses', ['email' => $this->fixture['reader']->email, 'permission' => 'unit.view', 'unit_id' => $this->fixture['hidden'], 'reason' => 'Synthetic test'])->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_grants_and_revocations_take_effect_on_next_request(): void
|
||||
{
|
||||
$this->login(); $token = $this->select(unit: $this->fixture['unit']);
|
||||
$payload = ['email' => $this->fixture['reader']->email, 'permission' => 'company.edit', 'reason' => 'Synthetic test'];
|
||||
$this->withHeader('X-SGF-Context', $token)->postJson('/api/accesses', $payload)->assertOk();
|
||||
$this->withHeader('X-SGF-Context', $token)->postJson('/api/accesses/revoke', $payload)->assertOk();
|
||||
$this->login('reader'); $readerToken = $this->select(unit: $this->fixture['unit']);
|
||||
$this->withHeader('X-SGF-Context', $readerToken)->patchJson('/api/companies/'.$this->fixture['company'], ['name' => 'Denied'])->assertForbidden();
|
||||
DB::table('permission_grants')->where('user_id', $this->fixture['reader']->id)->where('permission', 'company.view')->delete();
|
||||
$this->getJson('/api/context')->assertForbidden();
|
||||
}
|
||||
|
||||
public function test_password_recovery_version_invalidates_existing_session(): void
|
||||
{
|
||||
$this->login(); $this->select();
|
||||
DB::table('users')->where('id', $this->fixture['manager']->id)->increment('auth_version');
|
||||
$this->app['auth']->forgetGuards();
|
||||
$this->getJson('/api/context')->assertUnauthorized();
|
||||
}
|
||||
|
||||
public function test_database_blocks_bulk_transfer_of_unit_ownership(): void
|
||||
{
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
$this->expectExceptionMessage('SGF_IMMUTABLE_OWNERSHIP');
|
||||
DB::table('units')->where('id', $this->fixture['unit'])->update(['company_id' => $this->fixture['sibling']]);
|
||||
}
|
||||
|
||||
public function test_database_blocks_bulk_transfer_of_company_ownership(): void
|
||||
{
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
$this->expectExceptionMessage('SGF_IMMUTABLE_OWNERSHIP');
|
||||
DB::table('companies')->where('id', $this->fixture['company'])->update(['tenant_id' => $this->fixture['otherTenant']]);
|
||||
}
|
||||
|
||||
public function test_audit_is_append_only(): void
|
||||
{
|
||||
$this->login();
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
$this->expectExceptionMessage('SGF_APPEND_ONLY_AUDIT');
|
||||
DB::table('identity_audit')->delete();
|
||||
}
|
||||
|
||||
public function test_database_blocks_bulk_transfer_of_membership(): void
|
||||
{
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
$this->expectExceptionMessage('SGF_IMMUTABLE_OWNERSHIP');
|
||||
DB::table('company_memberships')->where('user_id', $this->fixture['reader']->id)->update(['company_id' => $this->fixture['sibling']]);
|
||||
}
|
||||
|
||||
public function test_database_blocks_bulk_transfer_of_permission(): void
|
||||
{
|
||||
$this->expectException(\Illuminate\Database\QueryException::class);
|
||||
$this->expectExceptionMessage('SGF_IMMUTABLE_OWNERSHIP');
|
||||
DB::table('permission_grants')->where('user_id', $this->fixture['reader']->id)->where('permission', 'unit.view')->update(['unit_id' => $this->fixture['hidden']]);
|
||||
}
|
||||
|
||||
public function test_local_provisioning_and_recovery_refuse_the_test_database(): void
|
||||
{
|
||||
$this->artisan('identity:provision', ['--email' => 'never-created@example.test', '--name' => 'Synthetic', '--operator' => 'Test', '--reason' => 'Test'])
|
||||
->assertExitCode(1);
|
||||
$this->artisan('identity:recover', ['--email' => $this->fixture['reader']->email, '--operator' => 'Test', '--reason' => 'Test'])
|
||||
->assertExitCode(1);
|
||||
$this->assertDatabaseMissing('users', ['email' => 'never-created@example.test']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Support;
|
||||
|
||||
use App\Models\User;
|
||||
use App\Modules\Identity\Services\Permissions;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
final class IdentityFixture
|
||||
{
|
||||
public static function create(): array
|
||||
{
|
||||
\App\Shared\Testing\TestingDatabaseGuard::assertSafe();
|
||||
$now = now();
|
||||
$tenant = DB::table('tenants')->insertGetId(['name' => 'Cliente de demonstração', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$otherTenant = DB::table('tenants')->insertGetId(['name' => 'Outro cliente sintético', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$company = DB::table('companies')->insertGetId(['tenant_id' => $tenant, 'name' => 'Empresa Horizonte — Demonstração', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$sibling = DB::table('companies')->insertGetId(['tenant_id' => $tenant, 'name' => 'Empresa Maré — Demonstração', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$foreign = DB::table('companies')->insertGetId(['tenant_id' => $otherTenant, 'name' => 'Empresa externa sintética', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$unit = DB::table('units')->insertGetId(['tenant_id' => $tenant, 'company_id' => $company, 'name' => 'Unidade Central — Demonstração', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$hidden = DB::table('units')->insertGetId(['tenant_id' => $tenant, 'company_id' => $company, 'name' => 'Unidade Reservada', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$siblingUnit = DB::table('units')->insertGetId(['tenant_id' => $tenant, 'company_id' => $sibling, 'name' => 'Unidade Maré', 'created_at' => $now, 'updated_at' => $now]);
|
||||
$password = bin2hex(random_bytes(20));
|
||||
$manager = User::create(['name' => 'Gestor de Demonstração', 'email' => 'gestor@example.test', 'password' => $password]);
|
||||
$reader = User::create(['name' => 'Consulta de Demonstração', 'email' => 'consulta@example.test', 'password' => $password]);
|
||||
$limited = User::create(['name' => 'Gestor Limitado', 'email' => 'limitado@example.test', 'password' => $password]);
|
||||
foreach ([$manager, $reader, $limited] as $user) {
|
||||
DB::table('company_memberships')->insert(['user_id' => $user->id, 'tenant_id' => $tenant, 'company_id' => $company]);
|
||||
self::grant($user->id, $tenant, $company, 'company.view');
|
||||
self::grant($user->id, $tenant, $company, 'unit.view', $unit);
|
||||
}
|
||||
foreach (Permissions::COMPANY as $permission) { self::grant($manager->id, $tenant, $company, $permission); }
|
||||
self::grant($manager->id, $tenant, $company, 'unit.edit', $unit);
|
||||
self::grant($limited->id, $tenant, $company, 'access.manage');
|
||||
DB::table('company_memberships')->insert(['user_id' => $manager->id, 'tenant_id' => $tenant, 'company_id' => $sibling]);
|
||||
self::grant($manager->id, $tenant, $sibling, 'company.view');
|
||||
self::grant($manager->id, $tenant, $sibling, 'unit.view', $siblingUnit);
|
||||
return compact('tenant', 'otherTenant', 'company', 'sibling', 'foreign', 'unit', 'hidden', 'siblingUnit', 'manager', 'reader', 'limited', 'password');
|
||||
}
|
||||
|
||||
public static function grant(int $user, int $tenant, int $company, string $permission, ?int $unit = null): void
|
||||
{
|
||||
DB::table('permission_grants')->insertOrIgnore(['user_id' => $user, 'tenant_id' => $tenant, 'company_id' => $company,
|
||||
'permission' => $permission, 'unit_id' => $unit, 'created_at' => now(), 'updated_at' => now()]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace Tests;
|
||||
|
||||
use Illuminate\Foundation\Testing\TestCase as BaseTestCase;
|
||||
|
||||
abstract class TestCase extends BaseTestCase
|
||||
{
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
\App\Shared\Testing\TestingDatabaseGuard::assertSafe();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class ExampleTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* A basic test example.
|
||||
*/
|
||||
public function test_that_true_is_true(): void
|
||||
{
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Modules\Identity\Services\ProtectedProvisioningInput;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
|
||||
final class ProtectedProvisioningInputTest extends TestCase
|
||||
{
|
||||
private function stream(string $payload)
|
||||
{
|
||||
$stream = fopen('php://memory', 'r+');
|
||||
fwrite($stream, $payload); rewind($stream);
|
||||
return $stream;
|
||||
}
|
||||
|
||||
public function test_protected_input_preserves_unicode_and_spaces_without_echoing(): void
|
||||
{
|
||||
$secret = ' '.bin2hex(random_bytes(12)).'çã! ';
|
||||
$stream = $this->stream(json_encode(['confirmed' => true, 'email' => 'synthetic@example.test', 'password' => $secret, 'confirmation' => $secret]));
|
||||
try { $values = ProtectedProvisioningInput::read($stream, 'synthetic@example.test'); }
|
||||
finally { fclose($stream); }
|
||||
$this->assertTrue($values[0] === $secret && $values[1] === $secret);
|
||||
}
|
||||
|
||||
public function test_unconfirmed_or_wrong_recipient_is_refused_without_revealing_payload(): void
|
||||
{
|
||||
$secret = bin2hex(random_bytes(12));
|
||||
foreach ([['confirmed' => false, 'email' => 'synthetic@example.test'], ['confirmed' => true, 'email' => 'another@example.test']] as $case) {
|
||||
$stream = $this->stream(json_encode($case + ['password' => $secret, 'confirmation' => $secret]));
|
||||
try { ProtectedProvisioningInput::read($stream, 'synthetic@example.test'); $this->fail('Unconfirmed input accepted.'); }
|
||||
catch (RuntimeException $exception) { $this->assertStringNotContainsString($secret, $exception->getMessage()); }
|
||||
finally { fclose($stream); }
|
||||
}
|
||||
}
|
||||
|
||||
public function test_malformed_payload_is_refused_without_including_its_contents(): void
|
||||
{
|
||||
$stream = $this->stream('{private-invalid-payload');
|
||||
try {
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Entrada protegida inválida ou não confirmada. Nada alterado.');
|
||||
ProtectedProvisioningInput::read($stream, 'synthetic@example.test');
|
||||
} finally { fclose($stream); }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user