create(); $sector = new Sector; PortalAudit::save($sector, ['name' => 'Nome privado', 'slug' => 'test', 'description' => 'Texto confidencial', 'is_active' => true], $user->id); PortalAudit::save($sector, ['is_active' => false], $user->id); $events = DB::table('audit_events')->where('source_system', 'portal-management')->get(); $this->assertCount(2, $events); $this->assertSame($user->id, $events[0]->actor_user_id); $this->assertStringNotContainsString('Texto confidencial', json_encode($events)); $this->assertStringNotContainsString('Nome privado', json_encode($events)); $updated = $events->firstWhere('action', 'updated'); $this->assertFalse(json_decode($updated->after_data, true)['values']['is_active']); $this->actingAs($user)->get('/management/history')->assertForbidden(); config(['portal.managers' => [$user->email]]); $this->get('/management/history')->assertOk(); } public function test_audit_failure_rolls_back_the_business_change(): void { $sector = Sector::create(['name' => 'Energia', 'slug' => 'energia', 'is_active' => true]); try { PortalAudit::save($sector, ['is_active' => false], 9999999); $this->fail('Expected invalid actor foreign key to reject event.'); } catch (QueryException) { $this->assertTrue($sector->fresh()->is_active); } } public function test_public_notifications_are_disabled_by_default_and_enqueued_when_enabled(): void { $payload = ['fullName' => 'Pessoa de teste', 'email' => 'portal-integration-test@gmail.com', 'investorType' => 'empresa', 'country' => 'Moçambique', 'sectorInterest' => 'Energia', 'message' => 'Pedido de teste na base isolada.', 'privacyConsent' => true]; config(['portal.notifications_enabled' => false]); $this->postJson('/api/v1/interest-requests', $payload)->assertCreated(); $this->assertDatabaseCount('jobs', 0); config(['portal.notifications_enabled' => true, 'portal.notification_recipients' => ['team@example.invalid']]); $this->postJson('/api/v1/interest-requests', $payload)->assertCreated(); $this->assertDatabaseCount('jobs', 1); $this->assertDatabaseCount('interest_requests', 2); config(['queue.connections.database.after_commit' => true]); $this->postJson('/api/v1/interest-requests', $payload)->assertStatus(500); $this->assertDatabaseCount('jobs', 1); $this->assertDatabaseCount('interest_requests', 2); } public function test_job_checks_current_configuration_and_excludes_personal_data(): void { $interest = InterestRequest::create(['reference' => 'IR-TEST', 'full_name' => 'Pessoa confidencial', 'email' => 'personal@example.invalid', 'investor_type' => 'empresa', 'country' => 'Moçambique', 'sector_interest' => 'Energia', 'message' => 'Mensagem confidencial', 'privacy_consent' => true]); $job = new NotifyInterestReceived($interest->id, 'team@example.invalid'); config(['portal.notifications_enabled' => false]); Mail::shouldReceive('raw')->never(); $job->handle(); Mail::clearResolvedInstance('mail.manager'); Mail::swap(\Mockery::mock(MailManager::class)); config(['portal.notifications_enabled' => true, 'portal.notification_recipients' => ['team@example.invalid']]); Mail::shouldReceive('raw')->once()->withArgs(function ($body, $callback) { $this->assertStringContainsString('IR-TEST', $body); $this->assertStringContainsString('/management/interests/', $body); $this->assertStringNotContainsString('confidencial', $body); $this->assertStringNotContainsString('personal@example.invalid', $body); return is_callable($callback); }); $job->handle(); } public function test_readiness_reports_non_production_without_mutating_data(): void { $this->artisan('portal:readiness')->expectsOutput('[PENDENTE] Ambiente production')->assertExitCode(1); $this->assertDatabaseCount('users', 0); $this->assertDatabaseCount('jobs', 0); } }