assertTrue(IsolatedQaDatabase::allowsSchemaChanges()); $this->withoutVite(); [$this->author,$this->reviewer] = User::factory()->count(2)->create()->all(); config(['portal.managers' => [$this->author->email, $this->reviewer->email], 'portal.profile_access_enabled' => false, 'editorial_workflow.enabled' => true, 'editorial_workflow.cluster_competitiveness' => true, 'editorial_workflow.territorial_versions' => true, 'territorial.enabled' => true, 'territorial.editors' => [$this->author->email], 'territorial.reviewers' => [$this->reviewer->email], 'territorial.publishers' => [$this->reviewer->email]]); $this->actingAsWithMfa($this->reviewer); $this->actingAsWithMfa($this->author); } public function test_dashboard_navigation_uses_existing_scopes_and_does_not_expose_disabled_or_private_activities(): void { $service = app(ContentManagement::class); $items = $service->dashboardNavigation($this->author, $service->access($this->author)); $this->assertSame(['label' => 'Visão geral', 'href' => '/dashboard'], $items[0]); $this->assertNotContains('/management/clusters', array_column($items, 'href')); $this->assertContains('/management/clusters', array_column($service->administrationNavigation($this->author, $service->access($this->author)), 'href')); $this->assertContains('/dashboard#manifestacoes', array_column($items, 'href')); $outsider = User::factory()->create(); $this->assertSame([], $service->dashboardNavigation($outsider, [])); $this->assertSame([], $service->dashboardNavigation(null, [])); config(['editorial_workflow.enabled' => false]); $after = array_column($service->dashboardNavigation($this->author, $service->access($this->author)), 'href'); $this->assertContains('/dashboard', $after); $this->assertNotContains('/management/editorial', $after); } public function test_cluster_events_use_the_common_inbox_and_recheck_current_scope_and_event_integrity(): void { $cluster = EconomicCluster::create(['code' => 'NOTICE-'.Str::random(12), 'name' => 'Synthetic notice cluster', 'sector' => 'Energy', 'status' => 'planned']); $draft = ['version' => 1, 'clusterId' => $cluster->id, 'sector' => 'Energy', 'region' => 'Synthetic territory', 'forces' => []]; foreach (ClusterCompetitiveness::FORCES as $force) { $draft['forces'][$force] = ['pressure' => 'medium', ...array_fill_keys(ClusterCompetitiveness::FIELDS, 'Synthetic evidence and limitations')]; } $service = app(ClusterCompetitiveness::class); $service->change($cluster, $this->author->id, 0, 'saved', ['draft' => $draft]); $this->assertDatabaseMissing('portal_editorial_notifications', ['user_id' => $this->reviewer->id, 'action' => 'saved']); $state = $service->change($cluster, $this->author->id, 1, 'submitted', ['reason' => 'Synthetic review requested']); $notice = DB::table('portal_editorial_notifications')->where('user_id', $this->reviewer->id)->where('action', 'submitted')->first(); $this->assertNotNull($notice); $this->assertTrue(app(PortalEditorialWorkflow::class)->visibleNotice($this->reviewer, $notice)); app(PortalEditorialNativeNotices::class)->emit($this->author, $notice->native_event_id, [$this->reviewer->id]); $this->assertSame(1, DB::table('portal_editorial_notifications')->where('native_event_id', $notice->native_event_id)->where('user_id', $this->reviewer->id)->count()); $present = app(PortalEditorialNativeNotices::class)->present($notice); $this->assertStringContainsString($cluster->id.'/edit?notice_version=2', $present['href']); $this->assertSame('Competitividade — versão 2', $present['reference']); $this->actingAsWithMfa($this->reviewer)->postJson('/management/editorial/notices/'.$notice->id.'/read')->assertOk(); config(['portal.managers' => [$this->author->email]]); $this->assertFalse(app(PortalEditorialWorkflow::class)->visibleNotice($this->reviewer, $notice)); $this->postJson('/management/editorial/notices/'.$notice->id.'/read')->assertNotFound(); config(['portal.managers' => [$this->author->email, $this->reviewer->email]]); DB::table('portal_editorial_notifications')->where('id', $notice->id)->update(['content_hash' => str_repeat('0', 64)]); $tampered = DB::table('portal_editorial_notifications')->where('id', $notice->id)->first(); $this->assertFalse(app(PortalEditorialWorkflow::class)->visibleNotice($this->reviewer, $tampered)); } public function test_territory_events_open_the_correct_record_without_publishing_or_granting_access(): void { $source = (string) Str::uuid(); DB::table('geo_sources')->insert(['id' => $source, 'name' => 'Synthetic source', 'reference' => 'Synthetic source reference', 'checksum' => str_repeat('f', 64), 'source_version' => 'qa', 'collected_at' => today(), 'editor_id' => $this->author->id]); $registry = app(TerritorialRegistry::class); $id = $registry->save($this->author, ['name' => 'Synthetic country', 'code' => 'NOTICE-'.Str::random(12), 'location_type' => 'country', 'source_id' => $source, 'valid_from' => '2020-01-01', 'reason' => 'Synthetic territorial preparation']); $baseline = app(PortalEditorialNativeNotices::class)->pending($this->reviewer); $registry->transitionEditorial($this->author, $id, ['action' => 'pending_review', 'version_no' => 1, 'reason' => 'Synthetic territorial review requested']); $notice = DB::table('portal_editorial_notifications')->where('user_id', $this->reviewer->id)->where('action', 'pending_review')->first(); $this->assertNotNull($notice); $this->assertTrue(app(PortalEditorialWorkflow::class)->visibleNotice($this->reviewer, $notice)); $this->assertStringContainsString('territory='.$id, app(PortalEditorialNativeNotices::class)->present($notice)['href']); $this->actingAsWithMfa($this->reviewer)->get('/management/territorial/editorial?territory='.$id.'¬ice_version=2')->assertOk(); $this->assertFalse($registry->published()->where('g.id', $id)->exists()); $registry->transitionEditorial($this->reviewer, $id, ['action' => 'in_review', 'version_no' => 2, 'reason' => 'Synthetic reviewer starts review']); $this->assertSame($baseline['territories'], app(PortalEditorialNativeNotices::class)->pending($this->reviewer)['territories']); $registry->transitionEditorial($this->reviewer, $id, ['action' => 'archived', 'version_no' => 3, 'reason' => 'Synthetic proposal archived']); $this->assertSame($baseline['territories'] - 1, app(PortalEditorialNativeNotices::class)->pending($this->reviewer)['territories']); $this->assertNotEmpty(app(PortalEditorialWorkflow::class)->notices($this->author)); } public function test_email_designation_is_explicit_revocable_and_does_not_grant_content_management(): void { $outsider = User::factory()->create(); $workflow = app(PortalEditorialWorkflow::class); $this->assertFalse($workflow->legalReviewer($outsider)); config(['editorial_workflow.legal_reviewer_emails' => [strtolower($outsider->email)]]); $this->assertTrue($workflow->legalReviewer($outsider)); $this->assertFalse($outsider->can('manage-portal')); $outsider->forceFill(['email_verified_at' => null])->save(); $this->assertFalse($workflow->legalReviewer($outsider)); config(['editorial_workflow.legal_reviewer_emails' => []]); $this->assertFalse($workflow->legalReviewer($this->reviewer)); } }