116 lines
6.7 KiB
PHP
116 lines
6.7 KiB
PHP
<?php
|
|
|
|
namespace Tests\Feature;
|
|
|
|
use App\Models\InterestRequest;
|
|
use App\Models\Opportunity;
|
|
use App\Models\Sector;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Support\Str;
|
|
use Tests\TestCase;
|
|
|
|
class InterestSubmissionTest extends TestCase
|
|
{
|
|
use RefreshDatabase;
|
|
|
|
private function payload(): array
|
|
{
|
|
return ['fullName' => 'Pessoa de teste', 'email' => 'portal-integration-test@gmail.com',
|
|
'investorType' => 'empresa', 'country' => 'Moçambique', 'sectorInterest' => 'Energia',
|
|
'message' => 'Pedido de teste na base isolada.', 'privacyConsent' => true];
|
|
}
|
|
|
|
public function test_replay_reuses_original_confirmation_event_and_notification(): void
|
|
{
|
|
config(['portal.notifications_enabled' => true, 'portal.notification_recipients' => ['team@example.invalid']]);
|
|
$headers = ['Idempotency-Key' => (string) Str::uuid()];
|
|
$first = $this->postJson('/api/v1/interest-requests', $this->payload(), $headers)->assertCreated();
|
|
InterestRequest::firstOrFail()->update(['status' => 'closed']);
|
|
$second = $this->postJson('/api/v1/interest-requests', $this->payload(), $headers)->assertCreated();
|
|
$this->assertSame($first->json(), $second->json());
|
|
$this->assertDatabaseCount('interest_requests', 1);
|
|
$this->assertDatabaseCount('interest_submission_receipts', 1);
|
|
$this->assertDatabaseCount('outbox_events', 1);
|
|
$this->assertDatabaseCount('consents', 1);
|
|
$this->assertDatabaseHas('consents', ['interest_request_id' => InterestRequest::firstOrFail()->id,
|
|
'purpose' => 'respond_to_interest', 'status' => 'granted', 'policy_version' => '2026-09-30']);
|
|
$this->assertDatabaseCount('jobs', 1);
|
|
$event = DB::table('outbox_events')->first();
|
|
$this->assertSame('pending', $event->status);
|
|
$this->assertSame(InterestRequest::firstOrFail()->integration_id, $event->aggregate_id);
|
|
$this->assertEquals(['schema_version' => 1, 'interest_id' => $event->aggregate_id], json_decode($event->payload, true));
|
|
$this->assertStringNotContainsString('gmail', $event->payload);
|
|
}
|
|
|
|
public function test_key_reuse_with_changed_payload_is_conflict_and_invalid_keys_are_rejected(): void
|
|
{
|
|
$headers = ['Idempotency-Key' => (string) Str::uuid()];
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), $headers)->assertCreated();
|
|
$this->postJson('/api/v1/interest-requests', [...$this->payload(), 'message' => 'Uma mensagem diferente com outros dados.'], $headers)->assertStatus(409);
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), ['Idempotency-Key' => 'unsafe-key'])->assertUnprocessable();
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), ['Idempotency-Key' => ''])->assertUnprocessable();
|
|
$this->assertDatabaseCount('interest_requests', 1);
|
|
$this->assertDatabaseCount('outbox_events', 1);
|
|
}
|
|
|
|
public function test_proponent_sharing_is_a_separate_optional_versioned_consent(): void
|
|
{
|
|
$this->postJson('/api/v1/interest-requests', [...$this->payload(), 'shareWithProponent' => true])->assertCreated();
|
|
$interest = InterestRequest::firstOrFail();
|
|
$this->assertDatabaseHas('consents', ['interest_request_id' => $interest->id,
|
|
'purpose' => 'respond_to_interest', 'status' => 'granted']);
|
|
$this->assertDatabaseHas('consents', ['interest_request_id' => $interest->id,
|
|
'purpose' => 'share_contact_with_proponent', 'status' => 'granted', 'policy_version' => '2026-09-30']);
|
|
$this->assertSame(2, DB::table('consents')->where('interest_request_id', $interest->id)->count());
|
|
}
|
|
|
|
public function test_old_clients_still_work_and_distinct_keys_create_distinct_submissions(): void
|
|
{
|
|
$this->postJson('/api/v1/interest-requests', $this->payload())->assertCreated();
|
|
foreach ([Str::uuid(), Str::uuid()] as $key) {
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), ['Idempotency-Key' => (string) $key])->assertCreated();
|
|
}
|
|
$this->assertDatabaseCount('interest_requests', 3);
|
|
$this->assertDatabaseCount('outbox_events', 3);
|
|
$this->assertDatabaseCount('interest_submission_receipts', 2);
|
|
}
|
|
|
|
public function test_event_insert_failure_rolls_back_request_receipt_and_notification(): void
|
|
{
|
|
DB::statement('CREATE TRIGGER test_reject_outbox BEFORE INSERT ON outbox_events FOR EACH ROW EXECUTE FUNCTION investmoz_reject_audit_change()');
|
|
config(['portal.notifications_enabled' => true, 'portal.notification_recipients' => ['team@example.invalid']]);
|
|
$headers = ['Idempotency-Key' => (string) Str::uuid()];
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), $headers)->assertStatus(500);
|
|
$this->assertDatabaseCount('interest_requests', 0);
|
|
$this->assertDatabaseCount('interest_submission_receipts', 0);
|
|
$this->assertDatabaseCount('jobs', 0);
|
|
DB::statement('DROP TRIGGER test_reject_outbox ON outbox_events');
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), $headers)->assertCreated();
|
|
$this->assertDatabaseCount('outbox_events', 1);
|
|
}
|
|
|
|
public function test_schema_rollback_refuses_to_destroy_captured_identities(): void
|
|
{
|
|
$this->postJson('/api/v1/interest-requests', $this->payload(), ['Idempotency-Key' => (string) Str::uuid()])->assertCreated();
|
|
$migration = require database_path('migrations/2026_09_25_120000_add_interest_submission_receipts.php');
|
|
$this->expectException(\RuntimeException::class);
|
|
$this->expectExceptionMessage('Preserve submission identities');
|
|
$migration->down();
|
|
}
|
|
|
|
public function test_accepted_request_can_be_replayed_after_opportunity_is_archived(): void
|
|
{
|
|
$sector = Sector::create(['name' => 'Energia', 'slug' => 'energia']);
|
|
$opportunity = Opportunity::create(['sector_id' => $sector->id, 'reference' => 'INV-REPLAY', 'title' => 'Teste', 'slug' => 'teste-replay',
|
|
'status' => 'published', 'published_at' => now()->subDay()]);
|
|
$payload = [...$this->payload(), 'opportunityReference' => $opportunity->reference];
|
|
$headers = ['Idempotency-Key' => (string) Str::uuid()];
|
|
$first = $this->postJson('/api/v1/interest-requests', $payload, $headers)->assertCreated();
|
|
$opportunity->update(['status' => 'archived']);
|
|
$this->postJson('/api/v1/interest-requests', $payload, $headers)->assertCreated()->assertJsonPath('data.reference', $first->json('data.reference'));
|
|
$this->postJson('/api/v1/interest-requests', $payload, ['Idempotency-Key' => (string) Str::uuid()])->assertUnprocessable();
|
|
$this->assertDatabaseCount('interest_requests', 1);
|
|
}
|
|
}
|