164 lines
9.6 KiB
PHP
164 lines
9.6 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\InterestRequest;
|
|
use App\Models\Opportunity;
|
|
use App\Models\Sector;
|
|
use App\Models\User;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Inertia\Testing\AssertableInertia;
|
|
use Tests\TestCase;
|
|
|
|
class PortalManagementTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
public function test_catalog_filters_combine_and_survive_pagination(): void
|
|
{
|
|
$this->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);
|
|
}
|
|
}
|