withoutVite(); Http::fake(); Mail::fake(); Queue::fake(); $this->owner = User::factory()->create(); $this->actingAs($this->owner); $this->record = app(PartnershipExchange::class)->save($this->owner, ['client_key' => (string) Str::uuid(), 'title' => 'SYNTHETIC original draft', 'kind' => 'seeking', 'partnership_type' => 'joint_venture', 'offer' => 'SYNTHETIC offer', 'payload' => ['consent' => true, 'mandatory' => ['capacity']]]); } private function command(array $changes = []): array { return ['operation_key' => (string) Str::uuid(), 'version' => $this->record->version, 'kind' => 'seeking', 'title' => 'SYNTHETIC saved revision', 'partnership_type' => 'joint_venture', 'payload' => ['mandatory' => ['capacity']], ...$changes]; } private function url(): string { return '/parcerias/'.$this->record->id; } public function test_repeating_a_committed_edit_preserves_a_later_state_and_does_not_create_another_revision(): void { $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); $current = app(PartnershipExchange::class)->find($this->record->id); app(PartnershipExchange::class)->transition($this->owner, $current->id, $current->version, 'submit', 'SYNTHETIC submit after edit'); $events = DB::table('partnership_events')->count(); $audits = DB::table('audit_events')->count(); $notices = DB::table('partnership_notifications')->count(); $this->putJson($this->url(), $data)->assertOk()->assertJsonPath('replayed', true)->assertJsonPath('saved_version', $data['version'] + 1)->assertJsonPath('status', 'submitted'); $this->assertSame($events, DB::table('partnership_events')->count()); $this->assertSame($audits, DB::table('audit_events')->count()); $this->assertSame($notices, DB::table('partnership_notifications')->count()); } public function test_replay_does_not_revalidate_retired_type_or_criteria_as_a_new_write(): void { $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); config(['partnerships.types' => [], 'partnerships.criteria' => []]); $this->putJson($this->url(), $data)->assertOk()->assertJsonPath('replayed', true); } public function test_changed_payload_with_the_same_operation_key_is_a_conflict_without_new_events(): void { $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); $events = DB::table('partnership_events')->count(); $this->putJson($this->url(), [...$data, 'title' => 'SYNTHETIC changed content'])->assertConflict(); $this->assertSame($events, DB::table('partnership_events')->count()); $this->assertSame('SYNTHETIC saved revision', DB::table('portal_partnerships')->where('id', $this->record->id)->value('title')); } public function test_updates_use_distinct_operation_keys_without_changing_the_creation_identity(): void { $first = $this->command(['client_key' => (string) Str::uuid()]); $this->putJson($this->url(), $first)->assertOk()->assertJsonPath('replayed', false); $second = $this->command(['version' => $first['version'] + 1, 'title' => 'SYNTHETIC second revision', 'client_key' => $first['client_key']]); $this->putJson($this->url(), $second)->assertOk()->assertJsonPath('saved_version', $second['version'] + 1); $this->assertNotSame($first['operation_key'], $second['operation_key']); $this->assertSame($this->record->client_key, DB::table('portal_partnerships')->where('id', $this->record->id)->value('client_key')); } public function test_authority_is_required_for_recovery_even_for_a_revoked_original_author(): void { $org = (string) Str::uuid(); DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC editing organization', 'organization_type' => 'private']); DB::table('memberships')->insert(['user_id' => $this->owner->id, 'organization_id' => $org, 'role' => 'officer']); DB::table('portal_partnerships')->where('id', $this->record->id)->update(['organization_id' => $org]); $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); DB::table('memberships')->where('user_id', $this->owner->id)->where('organization_id', $org)->update(['role' => 'viewer']); $this->putJson($this->url(), $data)->assertForbidden(); $this->get($this->url().'/editar')->assertForbidden(); DB::table('memberships')->where('user_id', $this->owner->id)->where('organization_id', $org)->update(['deleted_at' => now()]); $this->putJson($this->url(), $data)->assertForbidden(); } public function test_editor_exposes_only_the_current_actors_save_keys_and_not_private_hashes(): void { $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); $url = $this->url().'/editar'; $version = app(HandleInertiaRequests::class)->version(Request::create($url)); $response = $this->get($url, ['X-Inertia' => 'true', 'X-Inertia-Version' => $version ?? ''])->assertOk()->assertJsonPath('props.editorActorId', $this->owner->id)->assertJsonPath('props.saveCommands.0.key', $data['operation_key'])->assertJsonPath('props.saveCommands.0.version', $data['version'] + 1); foreach (['operation_hash', 'command_hash', '_creation_hash', '_notification_context'] as $private) { $this->assertStringNotContainsString($private, $response->getContent()); } $org = (string) Str::uuid(); $other = User::factory()->create(); DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC shared draft', 'organization_type' => 'private']); DB::table('memberships')->insert([['user_id' => $this->owner->id, 'organization_id' => $org, 'role' => 'officer'], ['user_id' => $other->id, 'organization_id' => $org, 'role' => 'officer']]); DB::table('portal_partnerships')->where('id', $this->record->id)->update(['organization_id' => $org]); $this->actingAs($other)->get($url, ['X-Inertia' => 'true', 'X-Inertia-Version' => $version ?? ''])->assertOk()->assertJsonPath('props.saveCommands', [])->assertJsonPath('props.editorActorId', $other->id); } public function test_retired_sector_blocks_a_new_edit_but_not_a_read_only_replay(): void { $sector = Sector::create(['name' => 'SYNTHETIC edit sector', 'slug' => 'synthetic-'.Str::uuid(), 'is_active' => true]); $data = $this->command(['sector_id' => $sector->id]); $this->putJson($this->url(), $data)->assertOk(); $sector->update(['is_active' => false]); $this->putJson($this->url(), $data)->assertOk()->assertJsonPath('replayed', true); $this->putJson($this->url(), [...$data, 'operation_key' => (string) Str::uuid(), 'version' => $data['version'] + 1])->assertUnprocessable(); } public function test_invalid_or_stale_uncommitted_attempts_do_not_consume_a_key(): void { $data = $this->command(); $events = DB::table('partnership_events')->count(); $this->putJson($this->url(), [...$data, 'operation_key' => 'invalid'])->assertUnprocessable(); $this->putJson($this->url(), [...$data, 'version' => $data['version'] + 9])->assertConflict(); $this->assertSame($events, DB::table('partnership_events')->count()); $this->putJson($this->url(), $data)->assertOk()->assertJsonPath('replayed', false); } public function test_operation_uuid_case_and_unused_creation_key_do_not_change_the_committed_edit(): void { $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); $this->putJson($this->url(), [...$data, 'operation_key' => strtoupper($data['operation_key']), 'client_key' => (string) Str::uuid()])->assertOk()->assertJsonPath('replayed', true); } public function test_omitted_organization_and_payload_are_preserved_and_explicit_removal_is_a_new_command(): void { $org = (string) Str::uuid(); DB::table('organizations')->insert(['id' => $org, 'name' => 'SYNTHETIC source organization', 'organization_type' => 'private']); DB::table('memberships')->insert(['user_id' => $this->owner->id, 'organization_id' => $org, 'role' => 'officer']); DB::table('portal_partnerships')->where('id', $this->record->id)->update(['organization_id' => $org]); $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); $saved = app(PartnershipExchange::class)->find($this->record->id); $this->assertSame($org, $saved->organization_id); $this->assertTrue(json_decode($saved->payload, true)['consent']); $this->putJson($this->url(), [...$data, 'organization_id' => null])->assertConflict(); $this->putJson($this->url(), [...$data, 'operation_key' => (string) Str::uuid(), 'version' => $saved->version, 'organization_id' => null])->assertOk(); $this->assertNull(app(PartnershipExchange::class)->find($saved->id)->organization_id); } public function test_legacy_edits_without_operation_keys_keep_version_conflicts_instead_of_a_false_replay(): void { $data = $this->command(); unset($data['operation_key']); $this->putJson($this->url(), $data)->assertOk()->assertJsonPath('replayed', false)->assertJsonPath('saved_version', null); $this->putJson($this->url(), $data)->assertConflict(); } public function test_operation_keys_are_scoped_to_the_actor_and_partnership(): void { $data = $this->command(); $this->putJson($this->url(), $data)->assertOk(); $other = User::factory()->create(); $record = app(PartnershipExchange::class)->save($other, ['client_key' => (string) Str::uuid(), 'kind' => 'seeking']); $this->actingAs($other)->putJson('/parcerias/'.$record->id,[...$data, 'version' => $record->version])->assertOk()->assertJsonPath('replayed',false); $this->putJson($this->url(),$data)->assertForbidden(); } }