assertSame('simulator_fiscal_delta_20261021_test', $target->name); $this->assertSame(55441, (int) $target->port); $this->withoutVite(); config(['portal.profile_access_enabled' => true, 'portal.managers' => []]); $this->actor = User::factory()->create(); $this->actor->forceFill(['two_factor_secret' => Crypt::encryptString('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now()])->save(); $this->org = (string) Str::uuid(); DB::table('organizations')->insert(['id' => $this->org, 'name' => 'SYNTHETIC QA Tempo operator', 'external_id' => '000000000', 'organization_type' => 'private']); DB::table('audit_events')->insert(['source_system' => 'portal-profiles-v1', 'entity_type' => 'authorization', 'action' => 'operator_organization_registered', 'data_classification' => 'restricted', 'after_data' => json_encode(['organization_id' => $this->org, 'organization' => ['name' => 'SYNTHETIC QA Tempo operator', 'nuit' => '000000000'], 'source_reference' => 'Synthetic QA designation only'])]); app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), User::factory()->create(), ['user_id' => $this->actor->id, 'role' => 'promoter', 'organization_id' => $this->org, 'opportunity_types' => OpportunityManagement::CATEGORIES, 'responsibilities' => [], 'source' => 'internal-portal-designation']); $this->record = new Opportunity; $this->record->forceFill(['title' => 'PRIVATE TITLE', 'slug' => 'qa-promo-'.Str::uuid(), 'reference' => 'QA-'.Str::uuid(), 'status' => 'draft', 'opportunity_category' => 'investment_project', 'owner_user_id' => $this->actor->id, 'organization_id' => $this->org, 'management_version' => 1, 'promoter_name' => 'PRIVATE CONTACT', 'description' => 'PRIVATE CONTENT', 'estimated_investment_amount' => '123456.00', 'workflow_context' => ['operator_organization_id' => $this->org, 'contributors' => [$this->actor->id]], 'management_draft' => json_encode(['canonical' => ['official_title' => 'PRIVATE TITLE', 'contact' => 'PRIVATE CONTACT', 'documents' => ['PRIVATE DOCUMENT']]])])->save(); $this->login($this->actor); } private function login(User $actor): void { $this->actingAs($actor)->withSession(['portal.mfa_actor' => $actor->id, 'portal.mfa_at' => time(), 'portal.access_revision' => (int) $actor->portal_access_revision]); } private function base(): string { return '/minhas-oportunidades/'.$this->record->id.'/divulgacao'; } private function data(array $overrides = []): array { return [...['etag' => app(OpportunityManagement::class)->etag($this->record), 'action' => 'disclose', 'title' => 'SYNTHETIC QA public summary', 'summary' => 'Synthetic QA opportunity in preparation, no real investment.', 'description' => 'Only this deliberately selected public text.', 'source_reference' => 'Synthetic source provided for QA', 'edition' => 'QA-1', 'source_date' => '2026-10-22', 'limitations' => 'Financing and implementation not established.', 'territory_ids' => []], ...$overrides]; } private function preview(array $overrides = []): array { return $this->postJson($this->base().'/previa', $this->data($overrides))->assertOk()->json(); } private function confirm(array $preview, ?string $key = null): array { $data = ['token' => $preview['token'], 'operation_key' => $key ?? (string) Str::uuid(), 'confirm' => true, 'reason' => 'Synthetic QA operation reviewed in preview']; $this->postJson($this->base().'/confirmar', $data)->assertOk(); return $data; } public function test_preview_publish_update_withdraw_and_redisclose_preserve_private_draft_and_audit(): void { $file = (string) Str::uuid(); $attachment = (string) Str::uuid(); DB::table('documents')->insert(['id' => $file, 'file_name' => 'PRIVATE-QA-DOCUMENT.txt', 'storage_key' => 'private-qa/'.$file, 'media_type' => 'text/plain', 'sha256' => hash('sha256', 'private QA only'), 'size_bytes' => 15, 'uploaded_by' => $this->actor->id, 'status' => 'quarantined', 'data_classification' => 'internal']); DB::table('opportunity_documents')->insert(['id' => $attachment, 'opportunity_id' => $this->record->id, 'document_id' => $file, 'version' => 1, 'created_by' => $this->actor->id, 'payload' => json_encode(['title' => 'PRIVATE DOCUMENT', 'visibility' => 'internal'])]); $documentBefore = DB::table('documents')->where('id', $file)->first(); $attachmentBefore = DB::table('opportunity_documents')->where('id', $attachment)->first(); DB::table('opportunity_profiles')->insert(['opportunity_id' => $this->record->id, 'business_model' => 'PRIVATE PROFILE', 'source_reference' => 'PRIVATE SOURCE']); $before = $this->record->refresh()->getRawOriginal(); $private = $before['management_draft']; $preview = $this->preview(); $this->assertSame($before, $this->record->fresh()->getRawOriginal()); $this->assertFalse($preview['writes']); $this->assertStringContainsString('Em preparação', $preview['projection']['description']); $this->assertStringNotContainsString('PRIVATE', json_encode($preview['projection'])); $key = (string) Str::uuid(); $request = $this->confirm($preview, $key); $this->postJson($this->base().'/confirmar', $request)->assertOk()->assertJsonPath('replayed', true); $this->record->refresh(); $this->assertSame('draft', $this->record->workflow_context['working_state']); $this->assertSame($private, $this->record->management_draft); $this->assertSame('PRIVATE CONTACT', app(OpportunityManagement::class)->values($this->record)['promoter_name']); $public = $this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertOk(); $this->assertStringNotContainsString('PRIVATE', $public->getContent()); $public->assertJsonPath('data.maturity_label', 'Em preparação')->assertJsonPath('data.estimated_investment_amount', null); $context = app(\App\Services\InvestmentCatalogue::class)->context($this->record->id); $this->assertNull($context['profile']); $this->assertSame([], $context['source_evidence']); $this->assertStringNotContainsString('PRIVATE', json_encode($context)); $this->confirm($this->preview(['summary' => 'Updated synthetic QA summary.'])); $this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertOk()->assertJsonPath('data.summary', 'Updated synthetic QA summary.'); $this->get($this->base())->assertOk(); $this->confirm($this->preview(['action' => 'withdraw'])); $this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertNotFound(); $this->get($this->base())->assertOk(); $this->confirm($this->preview()); $this->getJson('/api/v1/opportunities/'.$this->record->slug)->assertOk(); $events = DB::table('audit_events')->where('source_system', 'tempo-preparation-publication')->where('after_data->record_id', $this->record->id)->get(); $this->assertCount(4, $events); $this->assertContains('promotional_summary_withdrawn', $events->pluck('action')->all()); foreach ($events as $event) { $this->assertSame($this->actor->id, (int) $event->actor_user_id); $this->assertFalse(json_decode($event->after_data, true)['execution_approved']); } $this->assertEquals($documentBefore, DB::table('documents')->where('id', $file)->first()); $this->assertEquals($attachmentBefore, DB::table('opportunity_documents')->where('id', $attachment)->first()); } public function test_stale_tampered_expired_and_foreign_preview_do_not_write(): void { $preview = $this->preview(); $payload = ['token' => $preview['token'].'broken', 'operation_key' => (string) Str::uuid(), 'confirm' => true, 'reason' => 'Synthetic QA invalid confirmation']; $this->postJson($this->base().'/confirmar', $payload)->assertUnprocessable(); $this->record->title = 'Concurrent private edit'; $this->record->save(); $this->postJson($this->base().'/confirmar', [...$payload, 'token' => $preview['token']])->assertConflict(); $fresh = $this->preview(); $this->travel(16)->minutes(); $this->withSession(['portal.mfa_at' => time()]); $this->postJson($this->base().'/confirmar', [...$payload, 'token' => $fresh['token']])->assertConflict(); $this->travelBack(); $this->login(User::factory()->create()); $this->get($this->base())->assertForbidden(); $this->postJson($this->base().'/confirmar', [...$payload, 'token' => $fresh['token']])->assertForbidden(); $this->assertSame('draft', $this->record->fresh()->status); } public function test_external_promoter_and_unrelated_internal_actor_cannot_disclose_and_no_ordinary_publication_is_overwritten(): void { $other = User::factory()->create(); $this->login($other); $this->postJson($this->base().'/previa', $this->data())->assertForbidden(); $this->login($this->actor); DB::table('memberships')->insert(['id' => (string) Str::uuid(), 'organization_id' => $this->org, 'user_id' => $this->actor->id, 'role' => 'officer']); DB::table('portal_role_assignments')->where('user_id', $this->actor->id)->update(['source' => 'approved-external-promoter']); $this->postJson($this->base().'/previa', $this->data())->assertForbidden(); DB::table('portal_role_assignments')->where('user_id', $this->actor->id)->update(['source' => 'internal-portal-designation']); $this->withSession(['portal.mfa_at' => time() - 901]); $this->postJson($this->base().'/previa', $this->data())->assertForbidden(); $this->login($this->actor); $this->record->forceFill(['status' => 'published', 'published_at' => now(), 'workflow_context' => [...$this->record->workflow_context, 'publication' => ['visibility' => 'public']]])->save(); $this->get($this->base())->assertForbidden(); } public function test_existing_publisher_scope_can_use_the_same_action_without_new_role_or_approval(): void { $publisher = User::factory()->create(); $publisher->forceFill(['two_factor_secret' => Crypt::encryptString('JBSWY3DPEHPK3PXP'), 'two_factor_confirmed_at' => now()])->save(); app(PortalProfileAdministration::class)->insertGrant((string) Str::uuid(), $this->actor, ['user_id' => $publisher->id, 'role' => 'publisher', 'organization_id' => $this->org, 'opportunity_types' => ['investment_project'], 'responsibilities' => [], 'source' => 'synthetic-approved-designation']); $roles = DB::table('portal_role_assignments')->count(); $this->login($publisher); $access = app(\App\Services\PortalProfileAccess::class); $this->assertTrue($access->canView($publisher, $this->record), 'Publisher read scope'); $this->assertTrue($access->scoped($publisher, 'publisher', $this->org, 'investment_project'), 'Publisher scope'); $this->assertTrue(app(\App\Services\OpportunityPromotion::class)->allowed($publisher, $this->record), 'Promotional action scope'); $this->confirm($this->preview()); $this->assertSame($roles, DB::table('portal_role_assignments')->count()); $this->assertArrayNotHasKey('approval', $this->record->fresh()->workflow_context); } public function test_profile_directory_uses_existing_accounts_without_exposing_authentication_secrets(): void { $other = User::factory()->create(); DB::table('portal_superuser_pair')->updateOrInsert(['id' => 1], ['first_user_id' => $this->actor->id, 'second_user_id' => $other->id, 'approved_by' => $other->id]); $this->get('/perfis/gestao')->assertOk()->assertInertia(fn (\Inertia\Testing\AssertableInertia $page) => $page ->has('staffAccounts')->where('staffAccounts', fn ($rows) => collect($rows)->contains(fn ($row) => $row['id'] === $this->actor->id && $row['email'] === $this->actor->email && $row['verified'] && $row['mfa']) && collect($rows)->every(fn ($row) => !isset($row['password']) && !isset($row['two_factor_secret'])))); } }