actingAs($this->manager()); $sector = $this->sector(); for ($i = 0; $i < 22; $i++) { Opportunity::create(['sector_id' => $sector->id, 'reference' => 'SOL-'.$i, 'title' => 'Solar '.$i, 'slug' => 'solar-'.$i, 'status' => $i === 21 ? 'draft' : 'published', 'published_at' => now()->subMinute()]); } $url = '/management/opportunities?q=SOLAR&status=published§or_id='.$sector->id; $this->get($url)->assertInertia(fn (AssertableInertia $page) => $page ->where('records.total', 21)->has('records.data', 20) ->where('records.next_page_url', fn ($url) => str_contains($url, 'status=published') && str_contains($url, 'q=SOLAR'))); $this->get($url.'&page=2')->assertInertia(fn (AssertableInertia $page) => $page ->has('records.data', 1)->where('records.data.0.reference', 'SOL-0')); $this->get('/management/opportunities?q=missing')->assertInertia(fn (AssertableInertia $page) => $page->where('records.total', 0)); $sector->update(['is_active' => false]); $this->get('/management/sectors?status=active')->assertInertia(fn (AssertableInertia $page) => $page->where('records.total', 0)); $this->get('/management/sectors?status=inactive&q=ENERGIA')->assertInertia(fn (AssertableInertia $page) => $page->where('records.total', 1)); $this->getJson('/management/opportunities?status=invalid')->assertUnprocessable(); $this->getJson('/management/opportunities?page=-1')->assertUnprocessable(); } protected function beforeRefreshingDatabase(): void { $this->assertSame('testing', app()->environment()); $this->assertStringEndsWith('_test', config('database.connections.pgsql.database')); } private function manager(): User { $user = User::factory()->create(); config(['portal.managers' => [strtolower($user->email)]]); return $user; } private function sector(): Sector { return Sector::create(['name' => 'Energia', 'slug' => 'energia', 'is_active' => true]); } private function opportunity(array $attributes = []): Opportunity { return Opportunity::create(array_merge([ 'sector_id' => $this->sector()->id, 'reference' => 'INV-TEST-001', 'title' => 'Energia solar', 'slug' => 'energia-solar', 'status' => 'published', 'published_at' => now()->subMinute(), ], $attributes)); } public function test_management_requires_an_explicit_verified_manager(): void { $this->get('/management/interests')->assertRedirect('/login'); $user = User::factory()->create(); $this->actingAs($user)->get('/management/interests')->assertForbidden(); $this->post('/management/sectors', ['name' => 'Intruso'])->assertForbidden(); config(['portal.managers' => [$user->email]]); $user->forceFill(['email_verified_at' => null])->save(); $this->get('/management/interests')->assertRedirect(route('verification.notice')); $this->actingAs($this->manager())->get('/management/interests')->assertOk(); } public function test_manager_creates_and_edits_sectors_and_invalidates_home_cache(): void { $this->actingAs($this->manager()); Cache::put('portal_home_pt', ['old' => true]); $data = ['name' => 'Energia', 'slug' => 'energia', 'sort_order' => 1, 'is_active' => true]; $this->post('/management/sectors', $data)->assertRedirect('/management/sectors'); $this->assertNull(Cache::get('portal_home_pt')); $sector = Sector::firstOrFail(); $this->put('/management/sectors/'.$sector->id, [...$data, 'is_active' => false])->assertRedirect(); $this->assertFalse($sector->fresh()->is_active); $this->post('/management/sectors', $data)->assertSessionHasErrors('slug'); $this->get('/management/sectors/create')->assertOk(); $this->get('/management/sectors/'.$sector->id.'/edit')->assertOk(); } public function test_opportunities_can_be_published_and_withdrawn_without_losing_money_precision(): void { $this->actingAs($this->manager()); $data = ['sector_id' => $this->sector()->id, 'reference' => 'INV-TEST', 'title' => 'Solar', 'slug' => 'solar', 'status' => 'draft', 'is_featured' => true, 'maturity_level' => 4, 'estimated_investment_amount' => '1234567890123456.78', 'estimated_investment_currency' => 'MZN']; $this->post('/management/opportunities', $data)->assertRedirect('/management/opportunities'); $opportunity = Opportunity::firstOrFail(); $this->getJson('/api/v1/opportunities/solar')->assertNotFound(); $this->put('/management/opportunities/'.$opportunity->id, [...$data, 'status' => 'published'])->assertRedirect(); $this->getJson('/api/v1/opportunities/solar')->assertOk() ->assertJsonPath('data.estimated_investment_amount', '1234567890123456.78') ->assertJsonPath('data.maturity_label', 'Projecto estruturado'); $this->getJson('/api/v1/home')->assertJsonPath('data.stats.opportunities', 1); $this->put('/management/opportunities/'.$opportunity->id, [...$data, 'status' => 'archived'])->assertRedirect(); $this->getJson('/api/v1/opportunities/solar')->assertNotFound(); $this->getJson('/api/v1/home')->assertJsonPath('data.stats.opportunities', 0); $this->put('/management/opportunities/'.$opportunity->id, [...$data, 'estimated_investment_amount' => '-1'])->assertSessionHasErrors('estimated_investment_amount'); } public function test_review_tracks_the_authenticated_reviewer_and_rejects_invalid_states(): void { $manager = $this->manager(); $interest = InterestRequest::create(['reference' => 'IR-TEST', 'full_name' => 'Pessoa de teste', 'email' => 'test@example.invalid', 'investor_type' => 'empresa', 'country' => 'Moçambique', 'sector_interest' => 'Energia', 'message' => 'Pedido de informação para teste.', 'privacy_consent' => true]); $this->actingAs($manager)->get('/management/interests/'.$interest->id)->assertOk(); $this->patch('/management/interests/'.$interest->id, ['status' => 'in_review', 'reviewed_by' => 999])->assertRedirect(); $this->assertSame($manager->id, $interest->fresh()->reviewed_by); $this->assertNotNull($interest->fresh()->reviewed_at); $this->patch('/management/interests/'.$interest->id, ['status' => 'invalid'])->assertSessionHasErrors('status'); } public function test_public_requests_reject_missing_draft_deleted_and_future_opportunities(): void { $opportunity = $this->opportunity(['status' => 'draft']); $payload = ['fullName' => 'Teste do portal', 'email' => 'invalid', 'investorType' => 'empresa', 'country' => 'Moçambique', 'sectorInterest' => 'Energia', 'message' => 'Pedido para verificar o contrato.', 'privacyConsent' => true, 'opportunityReference' => 'MISSING']; $this->postJson('/api/v1/interest-requests', $payload)->assertUnprocessable()->assertJsonValidationErrors('opportunityReference'); $payload['opportunityReference'] = $opportunity->reference; $this->postJson('/api/v1/interest-requests', $payload)->assertUnprocessable()->assertJsonValidationErrors('opportunityReference'); $opportunity->update(['status' => 'published', 'published_at' => now()->addDay()]); $this->getJson('/api/v1/opportunities/'.$opportunity->slug)->assertNotFound(); $this->postJson('/api/v1/interest-requests', $payload)->assertUnprocessable()->assertJsonValidationErrors('opportunityReference'); $opportunity->update(['published_at' => now()->subMinute()]); $opportunity->delete(); $this->postJson('/api/v1/interest-requests', $payload)->assertUnprocessable()->assertJsonValidationErrors('opportunityReference'); $this->assertDatabaseCount('interest_requests', 0); } public function test_valid_public_submissions_persist_and_return_distinct_references(): void { // DNS validation remains enabled; no mail is sent by this endpoint. $opportunity = $this->opportunity(); $payload = ['fullName' => 'Pessoa de teste', 'email' => 'portal-integration-test@gmail.com', 'investorType' => 'empresa', 'country' => 'Moçambique', 'sectorInterest' => 'Energia', 'message' => 'Pedido fictício na base isolada de testes.', 'privacyConsent' => true, 'opportunityReference' => $opportunity->reference]; $first = $this->postJson('/api/v1/interest-requests', $payload)->assertCreated()->json('data.reference'); $second = $this->postJson('/api/v1/interest-requests', $payload)->assertCreated()->json('data.reference'); $this->assertNotSame($first, $second); $this->assertMatchesRegularExpression('/^IR-\d{4}-[0-9A-Z]{26}$/', $first); $this->assertDatabaseHas('interest_requests', ['reference' => $first, 'opportunity_id' => $opportunity->id, 'privacy_consent' => true]); $this->postJson('/api/v1/interest-requests', [...$payload, 'privacyConsent' => false])->assertUnprocessable()->assertJsonValidationErrors('privacyConsent'); $this->assertDatabaseCount('interest_requests', 2); } }