Files
Investidor-V2/tests/Feature/InvestmentWorkspaceTest.php
T
nala 250ac35fd1
tests / ci (push) Canceled after 0s
first commit
2026-09-28 10:01:09 +02:00

126 lines
8.5 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\InterestRequest;
use App\Models\User;
use Database\Seeders\InvestmentReferenceSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia;
use Tests\TestCase;
class InvestmentWorkspaceTest extends TestCase
{
use RefreshDatabase;
private function draft(User $user): string
{
return $this->actingAs($user)->postJson('/api/v1/investment/submissions')->assertCreated()->json('id');
}
private function payload(int $version = 1): array
{
return ['version' => $version, 'payload' => ['full_name' => 'Investidor sintético', 'email' => 'qa@example.test', 'country' => 'Moçambique',
'profile_id' => DB::table('investment_reference_options')->where('group', 'investor_profile')->where('code', 'individual')->value('id'),
'participation_id' => DB::table('investment_reference_options')->where('group', 'participation')->where('code', 'invest')->value('id'),
'sector' => 'Educação', 'objective' => 'Objectivo exclusivamente sintético para teste.', 'horizon' => '12 meses', 'contact_consent' => true], 'needs' => []];
}
public function test_profile_and_company_are_private_versioned_and_do_not_grant_organization_access(): void
{
$user = User::factory()->create();
$this->actingAs($user)->putJson('/api/v1/investment/profile', ['version' => 0, 'payload' => ['country' => 'Moçambique', 'phone' => '123456', 'sector' => 'Educação']])->assertOk()->assertJsonPath('version', 1);
$this->putJson('/api/v1/investment/profile', ['version' => 0, 'payload' => ['country' => 'Outro']])->assertConflict();
$data = ['version' => 0, 'payload' => ['name' => 'Empresa sintética', 'country' => 'Moçambique', 'role' => 'Representante declarado']];
$company = $this->postJson('/api/v1/investment/companies', $data)->assertOk()->json('id');
$id = $this->postJson('/api/v1/investment/submissions', ['company_id' => $company])->assertCreated()->json('id');
$this->getJson('/api/v1/investment/submissions/'.$id)->assertJsonPath('submission.payload.organisation', 'Empresa sintética')->assertJsonPath('submission.payload.phone', '123456');
$this->assertStringNotContainsString('Empresa sintética', DB::table('investment_company_declarations')->value('payload'));
$this->assertStringNotContainsString('123456', DB::table('investment_accounts')->value('payload'));
$this->assertDatabaseCount('memberships', 0);
$this->assertDatabaseCount('organizations', 0);
config(['portal.managers' => [$user->email]]);
$this->get('/management/history')->assertInertia(fn (AssertableInertia $page) => $page
->has('events.data', 3)->where('events.data.0.after_data.values.version', 1));
$this->actingAs(User::factory()->create())->getJson('/api/v1/investment/profile')->assertJsonCount(0, 'companies');
$this->postJson('/api/v1/investment/companies/'.$company, [...$data, 'version' => 1])->assertNotFound();
$this->postJson('/api/v1/investment/submissions', ['company_id' => $company])->assertNotFound();
$this->putJson('/api/v1/investment/profile', ['version' => 0, 'payload' => ['is_admin' => true]])->assertUnprocessable();
}
public function test_conversation_is_encrypted_authorized_and_idempotent_with_private_notifications(): void
{
$this->seed(InvestmentReferenceSeeder::class);
$owner = User::factory()->create();
$manager = User::factory()->create();
config(['portal.managers' => [$manager->email]]);
$id = $this->draft($owner);
$message = ['client_key' => (string) Str::uuid(), 'body' => 'Mensagem privada sintética do investidor.'];
$url = '/api/v1/investment/submissions/'.$id.'/messages';
$this->postJson($url, $message)->assertConflict();
$this->postJson('/api/v1/investment/submissions/'.$id.'/submit', $this->payload())->assertOk();
$first = $this->postJson($url, $message)->assertOk()->json('id');
$this->postJson($url, $message)->assertJsonPath('id', $first);
$this->postJson($url, [...$message, 'body' => 'Outro conteúdo com a mesma chave.'])->assertConflict();
$this->assertDatabaseCount('investment_messages', 1);
$this->assertStringNotContainsString('Mensagem privada', DB::table('investment_messages')->value('body'));
$this->actingAs($manager)->getJson('/api/v1/investment/notifications')->assertJsonPath('unread', 1);
$this->postJson($url, ['client_key' => (string) Str::uuid(), 'body' => 'Resposta sintética da equipa.'])->assertOk();
$this->actingAs($owner)->getJson('/api/v1/investment/notifications')->assertJsonPath('unread', 2);
$this->getJson('/api/v1/investment/submissions/'.$id)->assertJsonCount(2, 'messages')->assertJsonPath('messages.1.sender_role', 'support');
$notification = DB::table('investment_notifications')->where('user_id', $owner->id)->first();
$this->postJson('/api/v1/investment/notifications/'.$notification->id.'/read')->assertOk();
$this->getJson('/api/v1/investment/notifications')->assertJsonPath('unread', 1);
$this->actingAs(User::factory()->create())->postJson($url, $message)->assertNotFound();
$this->postJson('/api/v1/investment/notifications/'.$notification->id.'/read')->assertNotFound();
config(['portal.managers' => []]);
$this->actingAs($manager)->getJson('/api/v1/investment/notifications')->assertJsonCount(0, 'notifications.data');
$this->getJson('/api/v1/investment/submissions/'.$id)->assertNotFound();
}
public function test_revisions_preserve_encrypted_prior_values_without_exposing_them_to_other_users(): void
{
$this->seed(InvestmentReferenceSeeder::class);
$owner = User::factory()->create();
$id = $this->draft($owner);
$this->putJson('/api/v1/investment/submissions/'.$id, $this->payload())->assertOk();
$next = $this->payload(2);
$next['payload']['objective'] = 'Objectivo posterior exclusivamente sintético.';
$this->postJson('/api/v1/investment/submissions/'.$id.'/submit', $next)->assertOk();
$this->assertDatabaseCount('investment_submission_revisions', 3);
$url = '/api/v1/investment/submissions/'.$id.'/revisions/2';
$this->getJson($url)->assertOk()->assertJsonPath('snapshot.payload.objective', $this->payload()['payload']['objective'])->assertHeader('Cache-Control', 'no-store, private');
$this->assertStringNotContainsString('Objectivo', DB::table('investment_submission_revisions')->where('version', 2)->value('snapshot'));
$this->actingAs(User::factory()->create())->getJson($url)->assertNotFound();
}
public function test_status_review_notifies_owner_once_per_effective_change(): void
{
$this->seed(InvestmentReferenceSeeder::class);
$owner = User::factory()->create();
$manager = User::factory()->create();
config(['portal.managers' => [$manager->email]]);
$id = $this->draft($owner);
$this->postJson('/api/v1/investment/submissions/'.$id.'/submit', $this->payload())->assertOk();
$interest = InterestRequest::firstOrFail();
$this->actingAs($manager)->patch('/management/interests/'.$interest->id, ['status' => 'in_review'])->assertRedirect();
$this->patch('/management/interests/'.$interest->id, ['status' => 'in_review'])->assertRedirect();
$this->patchJson('/api/v1/investment/admin/interests/'.$interest->id.'/status', ['status' => 'in_review'])->assertOk()->assertJsonPath('status', 'in_review');
$this->assertDatabaseCount('investment_notifications', 2);
$this->actingAs($owner)->getJson('/api/v1/investment/notifications')->assertJsonPath('unread', 2);
}
public function test_incomplete_text_can_be_saved_as_draft_but_cannot_be_submitted(): void
{
$owner = User::factory()->create();
$id = $this->draft($owner);
$partial = ['version' => 1, 'payload' => ['full_name' => 'A', 'email' => 'parcial@', 'objective' => 'Ideia']];
$this->putJson('/api/v1/investment/submissions/'.$id, $partial)->assertOk()->assertJsonPath('version', 2);
$this->getJson('/api/v1/investment/submissions/'.$id)->assertJsonPath('submission.payload.email', 'parcial@');
$this->postJson('/api/v1/investment/submissions/'.$id.'/submit', [...$partial, 'version' => 2])->assertUnprocessable();
$this->assertDatabaseCount('interest_requests', 0);
}
}