Files
Investidor-V2/tests/Feature/InvestmentProfileCommandsTest.php
T

108 lines
6.8 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Str;
use Inertia\Testing\AssertableInertia as Assert;
use Tests\TestCase;
final class InvestmentProfileCommandsTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->withoutVite();
Http::fake();
Mail::fake();
$this->actingAs(User::factory()->create());
}
private function company(): array
{
return ['version' => 0, 'operation_key' => (string) Str::uuid(), 'payload' => ['name' => 'SYNTHETIC private company', 'incorporation_country_code' => 'MZ', 'role' => 'SYNTHETIC declared representative']];
}
public function test_new_company_replay_returns_the_original_identity_and_preserves_ciphertext_and_audit(): void
{
$command = $this->company();
$first = $this->postJson('/api/v1/investment/companies', $command)->assertOk()->json();
$cipher = DB::table('investment_company_declarations')->value('payload');
$events = DB::table('audit_events')->count();
$this->postJson('/api/v1/investment/companies', $command)->assertExactJson($first);
$this->assertSame(1, DB::table('investment_company_declarations')->count());
$this->assertSame($cipher, DB::table('investment_company_declarations')->value('payload'));
$this->assertSame($events, DB::table('audit_events')->count());
$this->assertSame(0, DB::table('organizations')->count());
$this->assertSame(0, DB::table('memberships')->count());
$record = DB::table('audit_events')->where('source_system', 'investment-workspace-command')->sole();
$this->assertStringNotContainsString('SYNTHETIC private company', $record->after_data);
$metadata = json_decode($record->after_data, true);
$this->assertSame('SYNTHETIC private company', json_decode(Crypt::decryptString($metadata['command_encrypted']), true)['payload']['name']);
}
public function test_key_reuse_with_changed_content_is_a_conflict_and_cannot_change_the_original(): void
{
$command = $this->company();
$id = $this->postJson('/api/v1/investment/companies', $command)->assertOk()->json('id');
$before = DB::table('investment_company_declarations')->where('id', $id)->first();
$this->postJson('/api/v1/investment/companies', [...$command, 'payload' => [...$command['payload'], 'name' => 'SYNTHETIC changed']])->assertConflict();
$this->assertEquals($before, DB::table('investment_company_declarations')->where('id', $id)->first());
}
public function test_profile_commands_validate_iso_replay_without_increment_and_preserve_historical_country(): void
{
$command = ['version' => 0, 'operation_key' => (string) Str::uuid(), 'payload' => ['residence_country_code' => 'PT', 'country' => 'Historical country', 'phone' => 'SYNTHETIC-contact']];
$this->putJson('/api/v1/investment/profile', $command)->assertExactJson(['version' => 1]);
$before = DB::table('investment_accounts')->first();
$this->putJson('/api/v1/investment/profile', ['payload' => array_reverse($command['payload'], true), 'operation_key' => strtoupper($command['operation_key']), 'version' => 0])->assertExactJson(['version' => 1]);
$this->assertEquals($before, DB::table('investment_accounts')->first());
$this->putJson('/api/v1/investment/profile', ['version' => 1, 'operation_key' => (string) Str::uuid(), 'payload' => ['residence_country_code' => 'ZZ']])->assertUnprocessable()->assertJsonValidationErrors('payload.residence_country_code');
$this->getJson('/api/v1/investment/profile')->assertOk()->assertJsonPath('account.version', 1)->assertJsonPath('account.payload.residence_country_code', 'PT')->assertJsonPath('account.payload.country', 'Historical country')->assertHeader('Cache-Control', 'no-store, private');
$this->get('/investidor/profile')->assertOk()->assertHeader('Cache-Control', 'no-store, private')->assertInertia(fn (Assert $page) => $page->component('investment/profile')->where('actorId', auth()->id())->where('account.payload.residence_country_code', 'PT'));
$this->assertEquals($before, DB::table('investment_accounts')->first());
}
public function test_replay_after_later_updates_returns_the_original_receipt_without_reverting_current_data(): void
{
$command = $this->company();
$id = $this->postJson('/api/v1/investment/companies', $command)->assertOk()->json('id');
$update = ['version' => 1, 'operation_key' => (string) Str::uuid(), 'payload' => [...$command['payload'], 'name' => 'SYNTHETIC newer name']];
$this->postJson('/api/v1/investment/companies/'.$id, $update)->assertJsonPath('version', 2);
$before = DB::table('investment_company_declarations')->where('id', $id)->first();
$this->postJson('/api/v1/investment/companies', $command)->assertExactJson(['id' => $id, 'version' => 1]);
$this->postJson('/api/v1/investment/companies/'.$id, $update)->assertExactJson(['id' => $id, 'version' => 2]);
$this->assertEquals($before, DB::table('investment_company_declarations')->where('id', $id)->first());
$this->postJson('/api/v1/investment/companies/'.$id, [...$update, 'operation_key' => (string) Str::uuid()])->assertConflict();
}
public function test_commands_are_private_per_actor_and_do_not_identify_companies_by_name(): void
{
$command = $this->company();
$id = $this->postJson('/api/v1/investment/companies', $command)->assertOk()->json('id');
$this->actingAs(User::factory()->create());
$otherId = $this->postJson('/api/v1/investment/companies', $command)->assertOk()->json('id');
$this->assertNotSame($id, $otherId);
$this->postJson('/api/v1/investment/companies/'.$id, [...$command, 'version' => 1])->assertNotFound();
$this->getJson('/api/v1/investment/profile')->assertJsonCount(1, 'companies')->assertJsonPath('companies.0.id', $otherId);
$this->assertSame(2, DB::table('investment_company_declarations')->count());
}
public function test_malformed_keys_are_rejected_before_any_write_and_legacy_requests_remain_supported(): void
{
$command = $this->company();
$this->postJson('/api/v1/investment/companies', [...$command, 'operation_key' => 'bad'])->assertUnprocessable()->assertJsonValidationErrors('operation_key');
$this->assertSame(0, DB::table('investment_company_declarations')->count());
unset($command['operation_key']);
$this->postJson('/api/v1/investment/companies', $command)->assertOk();
$this->assertSame(1, DB::table('investment_company_declarations')->count());
}
}