Files
compras/tests/Feature/OrganizationActivationPreviewTest.php
T
2026-11-02 20:27:11 +02:00

123 lines
9.4 KiB
PHP

<?php
use App\Enums\PurchasingRole;
use App\Models\Tenant;
use App\Models\User;
use App\Services\OrganizationAccess;
use App\Services\OrganizationActivationPreview;
use App\Services\OrganizationConfiguration;
use App\Services\WorkContext;
use Illuminate\Support\Facades\DB;
use Inertia\Testing\AssertableInertia as Assert;
require_once __DIR__.'/../Support/ReceiptFixture.php';
function activationPreviewProfile(int $tenant, string $name, array $permissions, array $contexts = []): int
{
return DB::table('organization_profiles')->insertGetId(['tenant_id' => $tenant, 'name' => $name, 'permissions' => json_encode($permissions, JSON_THROW_ON_ERROR),
'work_contexts' => json_encode($contexts, JSON_THROW_ON_ERROR), 'version' => 1, 'created_at' => now(), 'updated_at' => now()]);
}
function activationPreviewMember(int $tenant, int $user, int $profile, array $extra = []): void
{
DB::table('organization_memberships')->insert(['tenant_id' => $tenant, 'user_id' => $user, 'profile_id' => $profile, 'maintenance' => false, 'created_at' => now(), 'updated_at' => now(), ...$extra]);
}
beforeEach(function () {
$this->f = receiptFixture();
$this->admin = $this->f['issuer'];
$this->admin->forceFill(['purchasing_role' => PurchasingRole::Admin])->save();
$this->second = User::factory()->create(['tenant_id' => $this->admin->tenant_id, 'purchasing_role' => PurchasingRole::Requester]);
$this->profile = activationPreviewProfile($this->admin->tenant_id, 'Administração sintética', ['admin']);
foreach ([$this->admin, $this->second] as $user) {
activationPreviewMember($user->tenant_id, $user->id, $this->profile);
}
DB::table('organization_access')->insert(['tenant_id' => $this->admin->tenant_id, 'active' => false, 'version' => 1, 'created_at' => now(), 'updated_at' => now()]);
$this->actingAs($this->admin);
$this->path = '/administracao/parametrizacao';
});
test('activation preview exposes access losses implicit request preparation and profile tasks without writing configuration', function () {
$before = [];
foreach (['organization_access', 'organization_profiles', 'organization_memberships', 'users', 'tenants', 'audit_logs', 'requisitions', 'purchase_orders'] as $table) {
$before[$table] = DB::table($table)->get()->toArray();
}
$this->get($this->path)->assertOk()->assertHeader('Cache-Control', 'max-age=0, no-store, private')
->assertInertia(fn (Assert $page) => $page->where('activationPreview.active', false)->where('activationPreview.version', 1)
->where('activationPreview.permanent_administrators', 2)->where('activationPreview.losing_access', 2)
->where('activationPreview.provisional_rules', true)->where('activationPreview.can_activate', false)
->where('workflowTasks.procurement.1.key', 'procurement.prepare'));
$rows = collect(app(OrganizationActivationPreview::class)->build($this->admin)['users'])->keyBy('id');
expect($rows[$this->second->id]['current_permissions'])->toBe(['requester'])
->and($rows[$this->second->id]['configured_permissions'])->toBe(['admin'])
->and($rows[$this->second->id]['current_can_request'])->toBeTrue()->and($rows[$this->second->id]['configured_can_request'])->toBeFalse()
->and($rows[$this->f['receiver']->id]['loses_access'])->toBeTrue();
foreach ($before as $table => $snapshot) {
expect(DB::table($table)->get()->toArray())->toEqual($snapshot);
}
});
test('activation preview agrees with the effective authority after explicit activation and preserves existing processes', function () {
DB::table('tenants')->where('id', $this->admin->tenant_id)->update(['rules_provisional' => false]);
$before = $this->f['r']->fresh()->toArray();
$preview = app(OrganizationActivationPreview::class)->build($this->admin);
expect($preview['can_activate'])->toBeTrue();
$this->post($this->path.'/activate', ['version' => $preview['version'], 'confirm' => true, 'reason' => 'Activação explícita apenas no ensaio sintético.'])->assertSessionHasNoErrors()->assertRedirect();
foreach ($preview['users'] as $person) {
$user = User::query()->findOrFail($person['id']);
expect(app(OrganizationAccess::class)->permissions($user))->toBe($person['configured_permissions'])
->and(app(OrganizationAccess::class)->canRequest($user))->toBe($person['configured_can_request']);
}
$this->get($this->path)->assertInertia(fn (Assert $page) => $page->where('activationPreview.active', true)->where('activationPreview.version', 2));
expect($this->f['r']->fresh()->toArray())->toBe($before)->and(DB::table('audit_logs')->where('action', 'organization.activate')->count())->toBe(1);
});
test('activation preview counts only permanent independent administrators and honours expiry and maintenance', function () {
DB::table('tenants')->where('id', $this->admin->tenant_id)->update(['rules_provisional' => false]);
DB::table('organization_memberships')->where('user_id', $this->second->id)->update(['expires_at' => now()->addHour()]);
$maintenance = activationPreviewProfile($this->admin->tenant_id, 'Consulta temporária sintética', ['warehouse']);
activationPreviewMember($this->admin->tenant_id, $this->f['receiver']->id, $maintenance, ['maintenance' => true, 'expires_at' => now()->addHour()]);
$preview = app(OrganizationActivationPreview::class)->build($this->admin);
$rows = collect($preview['users'])->keyBy('id');
expect($preview['permanent_administrators'])->toBe(1)->and($preview['can_activate'])->toBeFalse()
->and($rows[$this->f['receiver']->id]['configured_maintenance_only'])->toBeTrue();
$this->postJson($this->path.'/activate', ['version' => 1, 'confirm' => true, 'reason' => 'Não activar com administração permanente insuficiente.'])->assertUnprocessable()->assertJsonValidationErrors('administrators');
$this->travel(2)->hours();
$rows = collect(app(OrganizationActivationPreview::class)->build($this->admin)['users'])->keyBy('id');
expect($rows[$this->second->id]['configured_permissions'])->toBe([])
->and($rows[$this->f['receiver']->id]['configured_permissions'])->toBe([])
->and($rows[$this->f['receiver']->id]['configured_maintenance_only'])->toBeFalse()
->and(app(OrganizationAccess::class)->active($this->admin->tenant_id))->toBeFalse();
});
test('activation preview uses the same company licence context and functional intersection as runtime', function () {
DB::table('tenants')->where('id', $this->admin->tenant_id)->update(['rules_provisional' => false, 'work_context_policy' => json_encode(['revision' => 1, 'enabled' => ['procurement.internal', 'people.internal']], JSON_THROW_ON_ERROR)]);
config(['work_context.entitlements.'.$this->admin->tenant_id => ['procurement.internal']]);
$profile = activationPreviewProfile($this->admin->tenant_id, 'Preparação nas áreas autorizadas', ['requester', 'people_service_prepare'], ['procurement.internal', 'people.internal']);
activationPreviewMember($this->admin->tenant_id, $this->f['receiver']->id, $profile);
$preview = app(OrganizationActivationPreview::class)->build($this->admin);
$rows = collect($preview['users'])->keyBy('id');
expect($rows[$this->f['receiver']->id]['configured_contexts'])->toBe(['procurement.internal']);
$this->post($this->path.'/activate', ['version' => 1, 'confirm' => true, 'reason' => 'Activação sintética respeitando os contextos contratados.'])->assertSessionHasNoErrors();
expect(app(WorkContext::class)->available($this->f['receiver']))->toBe($rows[$this->f['receiver']->id]['configured_contexts']);
$this->actingAs($this->f['receiver'])->post('/contexto', ['area' => 'people', 'option' => 'people.internal'])->assertForbidden();
});
test('activation preview cannot be read by warehouse users or expose another company configuration', function () {
$this->actingAs($this->f['receiver'])->get($this->path)->assertForbidden();
$tenant = Tenant::query()->create(['name' => 'Outra organização sintética']);
$foreign = User::factory()->create(['tenant_id' => $tenant->id, 'purchasing_role' => PurchasingRole::Admin]);
$this->actingAs($foreign)->get($this->path)->assertInertia(fn (Assert $page) => $page->has('activationPreview.users', 1)
->where('activationPreview.users.0.id', $foreign->id)->where('activationPreview.permanent_administrators', 0));
});
test('activation preview is informational and cannot replace confirmation or an up to date version', function () {
DB::table('tenants')->where('id', $this->admin->tenant_id)->update(['rules_provisional' => false]);
$preview = app(OrganizationActivationPreview::class)->build($this->admin);
$this->postJson($this->path.'/activate', ['version' => 1, 'reason' => 'Não activar pela mera leitura do diagnóstico.'])->assertUnprocessable()->assertJsonValidationErrors('confirm');
app(OrganizationConfiguration::class)->execute($this->admin, 'profile', ['version' => 1, 'name' => 'Consulta sintética', 'permissions' => ['warehouse_request_read']]);
$this->postJson($this->path.'/activate', ['version' => $preview['version'], 'confirm' => true, 'reason' => 'Uma versão anterior não confirma alterações posteriores.'])->assertUnprocessable()->assertJsonValidationErrors('version');
expect(app(OrganizationAccess::class)->active($this->admin->tenant_id))->toBeFalse();
$this->get($this->path)->assertInertia(fn (Assert $page) => $page->where('activationPreview.version', 2));
});