create(); $this->actingAs($user) ->post('/contact-groups', [ 'name' => 'Benevoles', 'description' => 'Equipe locale', 'is_favorite' => '1', ]) ->assertRedirect('/contacts'); $group = ContactGroup::query()->where('name', 'Benevoles')->firstOrFail(); $response = $this->actingAs($user) ->post('/contacts', [ 'first_name' => 'Marie', 'last_name' => 'Durand', 'organization' => 'AFPMA', 'email' => 'marie@example.test', 'raw_phone_number' => '06 11 11 00 03', 'phone_label' => 'Mobile', 'group_ids' => [$group->id], 'sms_opt_out' => '1', 'is_favorite' => '1', ]); $contact = Contact::query()->where('email', 'marie@example.test')->firstOrFail(); $response->assertRedirect(route('contacts.show', $contact)); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'source' => 'local', 'sms_opt_out' => true, 'is_favorite' => true, ]); $this->assertDatabaseHas('contact_contact_group', [ 'contact_id' => $contact->id, 'contact_group_id' => $group->id, ]); $this->assertDatabaseHas('contact_phone_numbers', [ 'contact_id' => $contact->id, 'raw_number' => '06 11 11 00 03', 'normalized_number' => '+33611110003', 'country' => 'FR', 'is_valid' => true, 'is_mobile' => true, ]); } public function test_contacts_can_be_searched_by_name_and_phone_number(): void { $user = User::factory()->create(); $contact = Contact::query()->create([ 'source' => 'local', 'first_name' => 'Jean', 'last_name' => 'Martin', 'email' => 'jean@example.test', ]); $contact->phoneNumbers()->create([ 'raw_number' => '06 11 11 00 01', 'normalized_number' => '+33611110001', 'country' => 'FR', 'is_valid' => true, 'is_mobile' => true, 'is_primary' => true, ]); $this->actingAs($user) ->get('/contacts?q=Martin') ->assertOk() ->assertSee('Jean Martin') ->assertSee('+33611110001'); $this->actingAs($user) ->get('/contacts?q=11110001') ->assertOk() ->assertSee('Jean Martin'); } public function test_contacts_default_mobile_filter_includes_stale_normalized_mobile_numbers(): void { $user = User::factory()->create(); $contact = Contact::query()->create([ 'source' => 'carddav', 'external_id' => 'uid-yveline', 'first_name' => 'Yveline', 'last_name' => 'Mobile', ]); $contact->phoneNumbers()->create([ 'raw_number' => '064-201-4207', 'normalized_number' => '+33642014207', 'country' => 'FR', 'is_valid' => true, 'is_mobile' => false, 'is_primary' => true, ]); $this->actingAs($user) ->get('/contacts') ->assertOk() ->assertSee('Yveline Mobile') ->assertSee('Mobile detecte'); } public function test_authenticated_user_can_update_contact(): void { $user = User::factory()->create(); $oldGroup = ContactGroup::query()->create([ 'source' => 'local', 'name' => 'Anciens', 'slug' => 'anciens', ]); $newGroup = ContactGroup::query()->create([ 'source' => 'local', 'name' => 'Membres', 'slug' => 'membres', ]); $contact = Contact::query()->create([ 'source' => 'local', 'first_name' => 'Jean', 'last_name' => 'Martin', 'email' => 'jean@example.test', ]); $contact->groups()->attach($oldGroup); $contact->phoneNumbers()->create([ 'label' => 'Mobile', 'raw_number' => '06 11 11 00 01', 'normalized_number' => '+33611110001', 'country' => 'FR', 'is_valid' => true, 'is_mobile' => true, 'is_primary' => true, ]); $this->actingAs($user) ->put(route('contacts.update', $contact), [ 'first_name' => 'Jeanne', 'last_name' => 'Martin', 'email' => 'jeanne@example.test', 'raw_phone_number' => '07 55 55 00 04', 'phone_label' => 'Portable', 'group_ids' => [$newGroup->id], 'is_favorite' => '1', ]) ->assertRedirect(route('contacts.show', $contact)); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'first_name' => 'Jeanne', 'email' => 'jeanne@example.test', 'sms_opt_out' => false, 'is_favorite' => true, ]); $this->assertDatabaseMissing('contact_contact_group', [ 'contact_id' => $contact->id, 'contact_group_id' => $oldGroup->id, ]); $this->assertDatabaseHas('contact_contact_group', [ 'contact_id' => $contact->id, 'contact_group_id' => $newGroup->id, ]); $this->assertDatabaseHas('contact_phone_numbers', [ 'contact_id' => $contact->id, 'label' => 'Portable', 'raw_number' => '07 55 55 00 04', 'normalized_number' => '+33755550004', 'is_mobile' => true, ]); } public function test_imported_contacts_cannot_be_edited_or_deleted(): void { $user = User::factory()->create(); $contact = Contact::query()->create([ 'source' => 'carddav', 'external_id' => 'remote-1', 'first_name' => 'Jean', 'last_name' => 'Importe', ]); $this->actingAs($user) ->get(route('contacts.edit', $contact)) ->assertForbidden(); $this->actingAs($user) ->put(route('contacts.update', $contact), [ 'last_name' => 'Modifie', ]) ->assertForbidden(); $this->actingAs($user) ->delete(route('contacts.destroy', $contact)) ->assertForbidden(); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'last_name' => 'Importe', 'deleted_at' => null, ]); } public function test_authenticated_user_can_toggle_favorite_on_imported_contact(): void { $user = User::factory()->create(); $contact = Contact::query()->create([ 'source' => 'carddav', 'external_id' => 'remote-favorite', 'first_name' => 'Yveline', 'last_name' => 'Importee', 'is_favorite' => false, ]); $this->actingAs($user) ->patch(route('contacts.favorite', $contact), [ 'is_favorite' => '1', ]) ->assertRedirect(); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'source' => 'carddav', 'is_favorite' => true, ]); $this->actingAs($user) ->patch(route('contacts.favorite', $contact), [ 'is_favorite' => '0', ]) ->assertRedirect(); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'source' => 'carddav', 'is_favorite' => false, ]); } public function test_authenticated_user_can_toggle_sms_opt_out_on_imported_contact(): void { $user = User::factory()->create(); $contact = Contact::query()->create([ 'source' => 'galette', 'external_id' => 'remote-opt-out', 'first_name' => 'Claire', 'last_name' => 'Importee', 'sms_opt_out' => false, ]); $this->actingAs($user) ->patch(route('contacts.opt-out', $contact), [ 'sms_opt_out' => '1', ]) ->assertRedirect(); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'source' => 'galette', 'sms_opt_out' => true, ]); $this->actingAs($user) ->patch(route('contacts.opt-out', $contact), [ 'sms_opt_out' => '0', ]) ->assertRedirect(); $this->assertDatabaseHas('contacts', [ 'id' => $contact->id, 'source' => 'galette', 'sms_opt_out' => false, ]); } public function test_authenticated_user_can_delete_contact(): void { $user = User::factory()->create(); $contact = Contact::query()->create([ 'source' => 'local', 'first_name' => 'Jean', 'last_name' => 'Martin', ]); $this->actingAs($user) ->delete(route('contacts.destroy', $contact)) ->assertRedirect(route('contacts.index')); $this->assertSoftDeleted('contacts', [ 'id' => $contact->id, ]); } public function test_authenticated_user_can_update_and_delete_group(): void { $user = User::factory()->create(); $group = ContactGroup::query()->create([ 'source' => 'local', 'name' => 'Benevoles', 'slug' => 'benevoles', ]); $contact = Contact::query()->create([ 'source' => 'local', 'first_name' => 'Marie', 'last_name' => 'Durand', ]); $contact->groups()->attach($group); $this->actingAs($user) ->put(route('contact-groups.update', $group), [ 'name' => 'Equipe accueil', 'description' => 'Permanences', 'is_favorite' => '1', ]) ->assertRedirect(route('contacts.index', ['group' => $group->id])); $this->assertDatabaseHas('contact_groups', [ 'id' => $group->id, 'name' => 'Equipe accueil', 'slug' => 'equipe-accueil', 'description' => 'Permanences', 'is_favorite' => true, ]); $this->actingAs($user) ->delete(route('contact-groups.destroy', $group)) ->assertRedirect(route('contacts.index')); $this->assertSoftDeleted('contact_groups', [ 'id' => $group->id, ]); $this->assertDatabaseMissing('contact_contact_group', [ 'contact_id' => $contact->id, 'contact_group_id' => $group->id, ]); } public function test_authenticated_user_can_update_local_group_members(): void { $user = User::factory()->create(); $group = ContactGroup::query()->create([ 'source' => 'local', 'name' => 'Benevoles', 'slug' => 'benevoles', ]); $kept = Contact::query()->create([ 'source' => 'local', 'first_name' => 'Marie', 'last_name' => 'Durand', ]); $removed = Contact::query()->create([ 'source' => 'local', 'first_name' => 'Paul', 'last_name' => 'Ancien', ]); $addedImported = Contact::query()->create([ 'source' => 'carddav', 'external_id' => 'remote-added', 'first_name' => 'Claire', 'last_name' => 'Importee', ]); $group->contacts()->attach([$kept->id, $removed->id]); $this->actingAs($user) ->put(route('contact-groups.update', $group), [ 'name' => 'Benevoles', 'members_submitted' => '1', 'contact_ids' => [$kept->id, $addedImported->id], ]) ->assertRedirect(route('contacts.index', ['group' => $group->id])); $this->assertDatabaseHas('contact_contact_group', [ 'contact_id' => $kept->id, 'contact_group_id' => $group->id, ]); $this->assertDatabaseHas('contact_contact_group', [ 'contact_id' => $addedImported->id, 'contact_group_id' => $group->id, ]); $this->assertDatabaseMissing('contact_contact_group', [ 'contact_id' => $removed->id, 'contact_group_id' => $group->id, ]); } public function test_imported_groups_cannot_be_edited_or_deleted(): void { $user = User::factory()->create(); $group = ContactGroup::query()->create([ 'source' => 'galette', 'external_id' => 'remote-group', 'name' => 'Groupe importe', 'slug' => 'galette-groupe-importe', ]); $this->actingAs($user) ->get(route('contact-groups.edit', $group)) ->assertForbidden(); $this->actingAs($user) ->put(route('contact-groups.update', $group), [ 'name' => 'Modifie', ]) ->assertForbidden(); $this->actingAs($user) ->delete(route('contact-groups.destroy', $group)) ->assertForbidden(); $this->assertDatabaseHas('contact_groups', [ 'id' => $group->id, 'name' => 'Groupe importe', 'deleted_at' => null, ]); } public function test_guest_cannot_access_contacts(): void { $this->get('/contacts') ->assertRedirect('/login'); } }