seed(RolePermissionSeeder::class); } public function test_follow_up_endpoints_share_one_canonical_enveloped_contract(): void { $agent = $this->agent(); $lead = $this->lead($agent); $created = $this->actingAs($agent)->postJson('/api/follow-ups', [ 'lead_id' => $lead->id, 'scheduled_at' => now()->addHour()->toISOString(), 'notes' => 'Call customer after lunch', ])->assertCreated() ->assertJsonPath('data.lead_id', $lead->id) ->assertJsonPath('data.user_id', $agent->id) ->assertJsonPath('data.status', 'pending') ->assertJsonStructure(['data' => ['id', 'lead', 'assignee', 'scheduled_at', 'completed_at', 'notes', 'status', 'is_overdue'], 'meta', 'links', 'message']); $id = $created->json('data.id'); $this->actingAs($agent)->getJson('/api/follow-ups') ->assertOk() ->assertJsonPath('data.0.id', $id) ->assertJsonStructure(['data', 'meta' => ['current_page', 'last_page', 'per_page', 'total', 'from', 'to'], 'links']); $this->actingAs($agent)->patchJson("/api/follow-ups/{$id}/mark-done") ->assertOk() ->assertJsonPath('data.status', 'completed') ->assertJsonPath('data.is_overdue', false); } public function test_today_get_has_no_side_effect_and_scheduler_sends_due_notification_once(): void { $agent = $this->agent(); $lead = $this->lead($agent); $followUp = FollowUp::create([ 'lead_id' => $lead->id, 'user_id' => $agent->id, 'scheduled_at' => now()->subMinute(), 'status' => 'pending', ]); $this->actingAs($agent)->getJson('/api/follow-ups/today')->assertOk()->assertJsonPath('data.0.id', $followUp->id); $this->assertDatabaseCount('internal_notifications', 0); $service = app(FollowUpReminderService::class); $this->assertSame(1, $service->sendDueReminders()); $this->assertSame(0, $service->sendDueReminders()); $this->assertDatabaseHas('internal_notifications', ['user_id' => $agent->id, 'type' => 'overdue_follow_up']); } private function agent(): User { $agent = User::factory()->create(['is_active' => true]); $agent->assignRole('agent'); return $agent; } private function lead(User $agent): Lead { return Lead::create([ 'company' => 'Follow-up Co', 'first_name' => 'Mina', 'last_name' => 'Rahimi', 'phone' => fake()->unique()->numerify('021########'), 'assigned_to' => $agent->id, 'is_unassigned' => false, ]); } }