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)); });