user = User::factory()->create(); Sanctum::actingAs($this->user); } private function validPayload(array $overrides = []): array { return array_merge([ 'role' => 'receiver', 'label' => 'خانه', 'name' => 'علی رضایی', 'phone' => '09121234567', 'state' => 'تهران', 'city' => 'تهران', 'postal_code' => '1234567890', 'address' => 'خیابان ولیعصر، پلاک ۱۰، واحد ۲', ], $overrides); } // ═══════════════════ index ═══════════════════ #[Test] public function it_lists_only_current_user_addresses() { AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); AddressBookEntry::create([...$this->validPayload(['role' => 'sender', 'name' => 'رضا']), 'user_id' => $this->user->id]); $other = User::factory()->create(); AddressBookEntry::create([...$this->validPayload(['name' => 'کاربر دیگر']), 'user_id' => $other->id]); $response = $this->getJson('/api/v1/addresses'); $response->assertStatus(200) ->assertJson(['success' => true]) ->assertJsonCount(2, 'data'); } #[Test] public function it_filters_addresses_by_role() { AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); AddressBookEntry::create([...$this->validPayload(['role' => 'sender', 'name' => 'رضا']), 'user_id' => $this->user->id]); $response = $this->getJson('/api/v1/addresses?role=sender'); $response->assertStatus(200) ->assertJsonCount(1, 'data') ->assertJsonPath('data.0.role', 'sender'); } #[Test] public function it_requires_authentication() { // حذف کاربر actAs‌شده از گارد sanctum برای این تست auth('sanctum')->forgetUser(); $response = $this->getJson('/api/v1/addresses'); $response->assertStatus(401); } // ═══════════════════ store ═══════════════════ #[Test] public function it_creates_address_successfully() { $response = $this->postJson('/api/v1/addresses', $this->validPayload()); $response->assertStatus(201) ->assertJson(['success' => true]) ->assertJsonPath('data.name', 'علی رضایی') ->assertJsonPath('data.role', 'receiver'); $this->assertDatabaseHas('address_book_entries', [ 'user_id' => $this->user->id, 'phone' => '09121234567', 'role' => 'receiver', ]); } #[Test] public function it_rejects_invalid_role() { $response = $this->postJson('/api/v1/addresses', $this->validPayload(['role' => 'hacker'])); $response->assertStatus(422) ->assertJson(['success' => false]); } #[Test] public function it_rejects_missing_required_fields() { $response = $this->postJson('/api/v1/addresses', [ 'role' => 'receiver', // name, phone, address وجود ندارند ]); $response->assertStatus(422); $this->assertArrayHasKey('name', $response->json('errors')); $this->assertArrayHasKey('phone', $response->json('errors')); $this->assertArrayHasKey('address', $response->json('errors')); } #[Test] public function it_upserts_duplicate_instead_of_creating() { AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); // همان نقش + همان تلفن + همان آدرس، با شهر متفاوت $response = $this->postJson('/api/v1/addresses', $this->validPayload(['city' => 'کرج'])); $response->assertStatus(200) ->assertJson([ 'success' => true, 'updated_existing' => true, ]); $this->assertDatabaseCount('address_book_entries', 1); $this->assertDatabaseHas('address_book_entries', ['city' => 'کرج']); } #[Test] public function it_enforces_max_entries_limit() { for ($i = 0; $i < AddressBookEntry::MAX_ENTRIES_PER_USER; $i++) { AddressBookEntry::create([ ...$this->validPayload(['phone' => '091200000' . str_pad((string) $i, 2, '0', STR_PAD_LEFT)]), 'user_id' => $this->user->id, ]); } $response = $this->postJson('/api/v1/addresses', $this->validPayload(['phone' => '09129999999'])); $response->assertStatus(422) ->assertJson(['success' => false]); $this->assertDatabaseCount('address_book_entries', AddressBookEntry::MAX_ENTRIES_PER_USER); } // ═══════════════════ update / delete / default ═══════════════════ #[Test] public function it_updates_own_address() { $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); $response = $this->putJson("/api/v1/addresses/{$entry->id}", $this->validPayload(['city' => 'اصفهان'])); $response->assertStatus(200) ->assertJsonPath('data.city', 'اصفهان'); } #[Test] public function it_cannot_change_entry_role_on_update() { $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); $response = $this->putJson("/api/v1/addresses/{$entry->id}", $this->validPayload(['role' => 'sender'])); $response->assertStatus(200); $this->assertDatabaseHas('address_book_entries', [ 'id' => $entry->id, 'role' => 'receiver', // نقش تغییر نکرده ]); } #[Test] public function it_prevents_updating_others_address() { $other = User::factory()->create(); $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $other->id]); $response = $this->putJson("/api/v1/addresses/{$entry->id}", $this->validPayload()); $response->assertStatus(403); } #[Test] public function it_deletes_own_address() { $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); $response = $this->deleteJson("/api/v1/addresses/{$entry->id}"); $response->assertStatus(200) ->assertJson(['success' => true]); $this->assertDatabaseMissing('address_book_entries', ['id' => $entry->id]); } #[Test] public function it_prevents_deleting_others_address() { $other = User::factory()->create(); $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $other->id]); $response = $this->deleteJson("/api/v1/addresses/{$entry->id}"); $response->assertStatus(403); $this->assertDatabaseHas('address_book_entries', ['id' => $entry->id]); } #[Test] public function it_sets_default_and_unsets_previous_default_of_same_role() { $first = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id, 'is_default' => true]); $second = AddressBookEntry::create([...$this->validPayload(['phone' => '09129876543']), 'user_id' => $this->user->id]); $response = $this->postJson("/api/v1/addresses/{$second->id}/default"); $response->assertStatus(200) ->assertJson(['success' => true]); $this->assertTrue($second->refresh()->is_default); $this->assertFalse($first->refresh()->is_default); } #[Test] public function it_prevents_setting_default_on_others_address() { $other = User::factory()->create(); $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $other->id]); $response = $this->postJson("/api/v1/addresses/{$entry->id}/default"); $response->assertStatus(403); } // ═══════════════════ Model behaviour ═══════════════════ #[Test] public function it_increments_use_count_and_sets_last_used_at() { $entry = AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); $this->assertSame(0, $entry->use_count); $this->assertNull($entry->last_used_at); $entry->markUsed(); $this->assertSame(1, $entry->refresh()->use_count); $this->assertNotNull($entry->refresh()->last_used_at); } #[Test] public function it_finds_duplicate_by_role_phone_address() { AddressBookEntry::create([...$this->validPayload(), 'user_id' => $this->user->id]); $dup = AddressBookEntry::findDuplicate($this->user->id, 'receiver', '09121234567', 'خیابان ولیعصر، پلاک ۱۰، واحد ۲'); $this->assertNotNull($dup); // نقش متفاوت → تکراری نیست $notDup = AddressBookEntry::findDuplicate($this->user->id, 'sender', '09121234567', 'خیابان ولیعصر، پلاک ۱۰، واحد ۲'); $this->assertNull($notDup); } #[Test] public function it_returns_form_fields_with_role_prefix() { $sender = AddressBookEntry::create([...$this->validPayload(['role' => 'sender']), 'user_id' => $this->user->id]); $fields = $sender->toFormFieldData(); $this->assertSame('علی رضایی', $fields['sender_name']); $this->assertSame('09121234567', $fields['sender_phone']); $this->assertArrayNotHasKey('receiver_name', $fields); } }