CRM/backend/tests/Feature/CallNoteNotificationIntegri...

147 خطوط
6.1 KiB
PHP

<?php
namespace Tests\Feature;
use App\Models\Call;
use App\Models\Contact;
use App\Models\ContactPhone;
use App\Models\Lead;
use App\Models\Note;
use App\Models\Notification;
use App\Models\Task;
use App\Models\Team;
use App\Models\User;
use App\Services\LegacyCallNoteBackfillService;
use App\Services\TaskReminderService;
use Database\Seeders\RolePermissionSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;
class CallNoteNotificationIntegrityTest extends TestCase
{
use RefreshDatabase;
protected function setUp(): void
{
parent::setUp();
$this->seed(RolePermissionSeeder::class);
}
public function test_call_supports_multiple_scoped_notes_with_edit_delete_and_pin_permissions(): void
{
[$supervisor, $agent] = $this->teamUsers();
$call = $this->makeCall($agent);
$first = $this->actingAs($agent)->postJson("/api/calls/{$call->id}/notes", [
'content' => 'First note', 'type' => 'objection', 'visibility' => 'team',
])->assertCreated();
$second = $this->actingAs($agent)->postJson("/api/calls/{$call->id}/notes", [
'content' => 'Private note', 'type' => 'internal', 'visibility' => 'private',
])->assertCreated();
$this->actingAs($agent)->getJson("/api/calls/{$call->id}/notes")->assertOk()->assertJsonCount(2, 'data');
$this->actingAs($agent)->postJson('/api/notes/'.$first->json('data.id').'/pin')->assertForbidden();
$this->actingAs($supervisor)->postJson('/api/notes/'.$first->json('data.id').'/pin')->assertOk()->assertJsonPath('data.is_pinned', true);
$this->actingAs($supervisor)->getJson("/api/calls/{$call->id}/notes")->assertOk()->assertJsonCount(1, 'data');
$this->actingAs($agent)->patchJson('/api/notes/'.$second->json('data.id'), ['content' => 'Edited private note'])
->assertOk()->assertJsonPath('data.content', 'Edited private note');
$this->assertDatabaseHas('notes', ['id' => $second->json('data.id'), 'content' => 'Edited private note']);
$this->assertDatabaseHas('activity_logs', ['action' => 'call_note_edited', 'subject_id' => $second->json('data.id')]);
}
public function test_legacy_backfill_is_idempotent_and_task_reminders_do_not_repeat_or_notify_done_tasks(): void
{
$agent = $this->user('agent');
$call = $this->makeCall($agent, 'Legacy summary');
$backfill = app(LegacyCallNoteBackfillService::class);
$this->assertSame(1, $backfill->run());
$this->assertSame(0, $backfill->run());
$this->assertSame(1, Note::where('source_key', "legacy_call:{$call->id}")->count());
$due = Task::create([
'subject' => 'Due task', 'assigned_to' => $agent->id, 'created_by' => $agent->id,
'priority' => 'normal', 'status' => 'open', 'visibility' => 'private',
'due_at' => now()->subHour(), 'reminder_at' => now()->subHours(2),
]);
Task::create([
'subject' => 'Done task', 'assigned_to' => $agent->id, 'created_by' => $agent->id,
'priority' => 'normal', 'status' => 'done', 'visibility' => 'private',
'due_at' => now()->subHour(), 'reminder_at' => now()->subHours(2), 'completed_at' => now(),
]);
$service = app(TaskReminderService::class);
$service->sendDueNotifications();
$service->sendDueNotifications();
$this->assertDatabaseCount('internal_notifications', 2);
$this->assertDatabaseHas('internal_notifications', ['user_id' => $agent->id, 'type' => 'task_due']);
$this->assertDatabaseHas('internal_notifications', ['user_id' => $agent->id, 'type' => 'task_overdue']);
$notificationId = Notification::where('user_id', $agent->id)->value('id');
$this->actingAs($agent)->patchJson("/api/notifications/{$notificationId}/read")
->assertOk()->assertJsonPath('data.is_read', true);
$this->assertNotNull(Notification::find($notificationId)->read_at);
$this->assertTrue($due->fresh()->is_overdue);
}
public function test_contact_phone_update_preserves_historical_call_relation(): void
{
$admin = $this->user('admin');
$call = $this->makeCall($admin);
$phoneId = $call->contact_phone_id;
$contact = $call->contact;
$this->actingAs($admin)->putJson("/api/contacts/{$contact->id}", [
'name' => $contact->name,
'phones' => [[
'id' => $phoneId,
'phone' => '09129999999',
'type' => 'mobile',
'status' => 'active',
]],
])->assertOk();
$this->assertSame($phoneId, $call->fresh()->contact_phone_id);
$this->assertSame('09129999999', $call->fresh()->contactPhone->phone);
}
private function makeCall(User $user, ?string $legacyNotes = null): Call
{
$lead = Lead::create([
'first_name' => 'Ali', 'last_name' => 'Karimi', 'company' => 'Acme',
'phone' => fake()->unique()->numerify('021########'), 'assigned_to' => $user->id,
]);
$contact = Contact::create([
'lead_id' => $lead->id, 'name' => 'Contact', 'status' => 'active',
'is_primary' => true, 'created_by' => $user->id,
]);
$phone = ContactPhone::create([
'contact_id' => $contact->id, 'phone' => '09121111111', 'type' => 'mobile', 'status' => 'active',
]);
return Call::create([
'lead_id' => $lead->id, 'contact_id' => $contact->id, 'contact_phone_id' => $phone->id,
'user_id' => $user->id, 'direction' => 'outbound', 'phone' => $phone->phone, 'notes' => $legacyNotes,
]);
}
private function teamUsers(): array
{
$supervisor = $this->user('supervisor');
$agent = $this->user('agent');
$team = Team::create(['name' => 'Sales', 'supervisor_id' => $supervisor->id, 'is_active' => true]);
$team->members()->attach([$supervisor->id, $agent->id]);
return [$supervisor, $agent];
}
private function user(string $role): User
{
$user = User::factory()->create(['is_active' => true]);
$user->assignRole($role);
return $user;
}
}