108 lines
6.8 KiB
PHP
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());
|
|
}
|
|
}
|