assertSame('55441',getenv('DB_PORT')); $this->assertSame('onboarding_internal_20261011_191450_test',getenv('DB_DATABASE')); parent::setUp(); $this->assertStringContainsString('onboarding-qa-internal-',(new \ReflectionClass(PortalProfileAccess::class))->getFileName()); $this->assertSame(55441,(int)DB::selectOne('SELECT inet_server_port() AS port')->port); config(['portal.profile_administration_enabled'=>true,'portal.profile_access_enabled'=>false,'portal.organization_country_enabled'=>true]); } private function holder(): User { $a=User::factory()->withTwoFactor()->create();$b=User::factory()->withTwoFactor()->create(); DB::table('portal_superuser_pair')->insert(['id'=>1,'first_user_id'=>$a->id,'second_user_id'=>$b->id,'approved_by'=>$b->id]);return $a; } private function mfa(User $user): static { return $this->actingAs($user)->withSession(['portal.mfa_actor'=>$user->id,'portal.mfa_at'=>time(),'portal.access_revision'=>(int)$user->fresh()->portal_access_revision]); } private function organizationPayload(): array { return ['operation_key'=>(string)Str::uuid(),'name'=>'Tempo Lda','nuit'=>'400993114','contact'=>'+258868010880','director'=>'Osmane Nala','function'=>'portal_owner_operator','source_reference'=>self::REASON]; } private function register(User $holder): string { return $this->mfa($holder)->postJson('/perfis/organizacao-operadora',$this->organizationPayload())->assertCreated()->json('id'); } private function assignment(User $subject,string $operator,string $role='promoter'): array { return ['internal_designation'=>true,'operation_key'=>(string)Str::uuid(),'user_id'=>$subject->id,'role'=>$role, 'operator_organization_id'=>$operator,'organization_id'=>$role==='promoter'?$operator:null, 'opportunity_types'=>OpportunityCanonicalContract::BASE_CATEGORIES,'confirmed_internal_designation'=>true,'reason'=>self::REASON]; } public function test_master_registration_replay_and_recovery_use_one_identity_without_memberships(): void { $h=$this->holder();$payload=$this->organizationPayload(); $id=$this->mfa($h)->postJson('/perfis/organizacao-operadora',$payload)->assertCreated()->json('id'); $this->postJson('/perfis/organizacao-operadora',$payload)->assertCreated()->assertJsonPath('id',$id); $this->postJson('/perfis/organizacao-operadora',[...$payload,'contact'=>'+258868010881'])->assertConflict(); $this->assertDatabaseCount('organizations',1);$this->assertDatabaseCount('memberships',0); $this->assertSame(1,DB::table('audit_events')->where('action','operator_organization_registered')->count()); $this->assertSame('+258868010880',app(PortalInternalDesignation::class)->organization($id)['contact']); $this->get('/perfis/gestao')->assertOk(); } public function test_existing_comma_alias_preserves_country_original_source_and_audited_previous_values(): void { $h=$this->holder();$id=(string)Str::uuid(); DB::table('organizations')->insert(['id'=>$id,'name'=>'Tempo, Lda','source_system'=>'existing-approved-master','organization_type'=>'private','incorporation_country_code'=>'MZ']); $this->mfa($h)->postJson('/perfis/organizacao-operadora',$this->organizationPayload())->assertCreated()->assertJsonPath('id',$id); $this->assertDatabaseCount('organizations',1); $this->assertDatabaseHas('organizations',['id'=>$id,'name'=>'Tempo Lda','source_system'=>'existing-approved-master','incorporation_country_code'=>'MZ']); $before=json_decode(DB::table('audit_events')->where('action','operator_organization_registered')->first()->after_data,true)['previous_master_values']; $this->assertSame('Tempo, Lda',$before['name']); $this->assertSame($id,app(PortalInternalDesignation::class)->organizations()[0]['id']); } public function test_three_internal_roles_are_independent_and_do_not_require_external_promoter_documents(): void { $h=$this->holder();$operator=$this->register($h); foreach(['promoter','validator','publisher'] as $role){ $subject=User::factory()->withTwoFactor()->create();$payload=$this->assignment($subject,$operator,$role); $id=$this->mfa($h)->postJson('/perfis/atribuicoes',$payload)->assertCreated()->json('id'); $this->postJson('/perfis/atribuicoes',$payload)->assertCreated()->assertJsonPath('id',$id); $this->assertDatabaseHas('portal_role_assignments',['id'=>$id,'role'=>$role,'source'=>'internal-portal-designation','organization_id'=>$role==='promoter'?$operator:null]); $this->mfa($subject)->get('/minhas-oportunidades')->assertNotFound(); } $this->assertDatabaseCount('memberships',0);$this->assertDatabaseCount('documents',0);$this->assertDatabaseCount('portal_profile_requests',0); $this->assertDatabaseCount('portal_role_assignments',3); $this->assertSame(3,DB::table('audit_events')->where('action','role_granted')->count()); $this->assertDatabaseCount('opportunities',0); } public function test_internal_designation_still_enforces_security_and_idempotency(): void { $h=$this->holder();$operator=$this->register($h);$subject=User::factory()->withTwoFactor()->create(); $this->mfa($h)->postJson('/perfis/atribuicoes',$this->assignment($h,$operator))->assertForbidden(); $unverified=User::factory()->unverified()->withTwoFactor()->create(); $this->postJson('/perfis/atribuicoes',$this->assignment($unverified,$operator))->assertUnprocessable(); $unenrolled=User::factory()->create();$this->postJson('/perfis/atribuicoes',$this->assignment($unenrolled,$operator))->assertUnprocessable(); $p=$this->assignment($subject,$operator);$this->postJson('/perfis/atribuicoes',[...$p,'confirmed_internal_designation'=>false])->assertUnprocessable(); $id=$this->postJson('/perfis/atribuicoes',$p)->assertCreated()->json('id'); $this->postJson('/perfis/atribuicoes',[...$p,'role'=>'publisher'])->assertConflict(); $this->postJson('/perfis/atribuicoes',[...$p,'operation_key'=>(string)Str::uuid()])->assertConflict(); $this->postJson('/perfis/atribuicoes',['user_id'=>$subject->id,'role'=>'promoter','opportunity_types'=>['investment_project'],'reason'=>self::REASON])->assertUnprocessable(); $this->postJson('/perfis/atribuicoes/'.$id.'/revogar',['reason'=>self::REASON])->assertOk(); $this->assertFalse(app(PortalProfileAccess::class)->has($subject,'promoter')); $this->postJson('/perfis/atribuicoes',$p)->assertConflict(); $this->actingAs($subject)->withSession(['portal.access_revision'=>0])->getJson('/perfil/promotor')->assertUnauthorized(); } public function test_internal_promoter_six_drafts_keep_real_promoter_and_do_not_grant_company_management(): void { $h=$this->holder();$operator=$this->register($h);$subject=User::factory()->withTwoFactor()->create(); $this->mfa($h)->postJson('/perfis/atribuicoes',$this->assignment($subject,$operator))->assertCreated(); $real=(string)Str::uuid();DB::table('organizations')->insert(['id'=>$real,'name'=>'Synthetic actual promoter QA','organization_type'=>'private']); config(['portal.profile_access_enabled'=>true,'portal.opportunity_dashboard_access_enabled'=>true]); $this->mfa($subject);$access=app(PortalProfileAccess::class); foreach(OpportunityCanonicalContract::BASE_CATEGORIES as $type){ $payload=['creation_key'=>(string)Str::uuid(),'opportunity_category'=>$type,'organization_id'=>$real,'title'=>'Synthetic '.$type,'reason'=>self::REASON]; $r=app(OpportunityManagement::class)->save($subject,$payload); $this->assertSame($real,$r->organization_id);$this->assertSame($operator,$r->workflow_context['operator_organization_id']); $this->assertTrue($access->canView($subject,$r));$this->assertTrue($access->canEdit($subject,$r)); $this->assertTrue($access->conflict($subject,$r)); $this->assertSame($r->id,app(OpportunityManagement::class)->save($subject,$payload)->id); $this->assertSame($real,Opportunity::findOrFail($r->id)->organization_id); } $this->assertDatabaseCount('opportunities',6);$this->assertDatabaseCount('memberships',0); $this->expectException(\Symfony\Component\HttpKernel\Exception\HttpException::class); app(OrganizationCountry::class)->authorize($subject,$operator,true); } public function test_accumulating_roles_cannot_turn_read_access_into_editing_an_unassigned_private_record(): void { $h=$this->holder();$operator=$this->register($h);$subject=User::factory()->withTwoFactor()->create(); $this->mfa($h)->postJson('/perfis/atribuicoes',$this->assignment($subject,$operator))->assertCreated(); $this->postJson('/perfis/atribuicoes',$this->assignment($subject,$operator,'validator'))->assertCreated(); config(['portal.profile_access_enabled'=>true]); $record=new Opportunity;$record->forceFill(['id'=>123,'owner_user_id'=>999,'organization_id'=>$operator,'opportunity_category'=>'investment_project','status'=>'draft','workflow_context'=>['operator_organization_id'=>$operator,'contributors'=>[999]]]); $access=app(PortalProfileAccess::class);$this->assertTrue($access->canView($subject,$record));$this->assertFalse($access->canEdit($subject,$record)); $only=User::factory()->withTwoFactor()->create();$this->mfa($h)->postJson('/perfis/atribuicoes',$this->assignment($only,$operator))->assertCreated(); $this->assertFalse($access->canView($only,$record)); } }