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()); } }