seed(DatabaseSeeder::class); }); test('assigned support user can change status of their assigned ticket and it logs activity', function (): void { $user = User::factory()->create(); $user->assignRole('user'); $supportUser = User::factory()->create(); $supportUser->assignRole('user'); // normal user assigned as support staff // Create ticket via service $dto = new CreateTicketRequest( userId: $user->id, connectedAppId: null, issueCategory: 'Technical Issue', description: 'SSO login error.', notifiedRoleIds: [], notifiedUserIds: [], attachment: null, ); $service = app(TicketService::class); $ticketData = $service->create($dto); $ticket = Ticket::find($ticketData->id); // Assign the ticket to the support user $service->assignUser($ticket->id, $supportUser->id); // Act as the assigned support user $this->actingAs($supportUser); // Status change should be successful $service->updateStatus($ticket->id, TicketStatusEnum::Hold->value); expect($ticket->fresh()->status->name)->toBe(TicketStatusEnum::Hold->value); // Verify activity log $activity = Activity::where('subject_id', $ticket->id) ->where('event', 'status_changed') ->latest() ->first(); expect($activity)->not->toBeNull() ->and($activity->causer_id)->toBe($supportUser->id) ->and($activity->properties['old_status'])->toBe(TicketStatusEnum::Open->value) ->and($activity->properties['new_status'])->toBe(TicketStatusEnum::Hold->value) ->and($activity->description)->toContain('Status updated from open to hold'); }); test('unauthorized users cannot change status of ticket', function (): void { $user = User::factory()->create(); $user->assignRole('user'); $otherUser = User::factory()->create(); $otherUser->assignRole('user'); $dto = new CreateTicketRequest( userId: $user->id, connectedAppId: null, issueCategory: 'Technical Issue', description: 'SSO login error.', notifiedRoleIds: [], notifiedUserIds: [], attachment: null, ); $service = app(TicketService::class); $ticketData = $service->create($dto); // Act as other user $this->actingAs($otherUser); // Changing status should fail with authorization exception $this->expectException(Illuminate\Auth\Access\AuthorizationException::class); $service->updateStatus($ticketData->id, TicketStatusEnum::Hold->value); }); test('commenting on a ticket logs comment activity and status transition', function (): void { $user = User::factory()->create(); $user->assignRole('user'); $dto = new CreateTicketRequest( userId: $user->id, connectedAppId: null, issueCategory: 'Technical Issue', description: 'SSO login error.', notifiedRoleIds: [], notifiedUserIds: [], attachment: null, ); $service = app(TicketService::class); $ticketData = $service->create($dto); $ticket = Ticket::find($ticketData->id); $this->actingAs($user); $replyDto = new PostReplyRequest( ticketId: $ticket->id, userId: $user->id, message: 'This is a test comment.', ); $service->postReply($replyDto); // Verify comment activity log $commentActivity = Activity::where('subject_id', $ticket->id) ->where('event', 'comment_posted') ->first(); expect($commentActivity)->not->toBeNull() ->and($commentActivity->properties['message'])->toBe('This is a test comment.') ->and($commentActivity->description)->toContain('Comment posted on ticket'); // Verify transition status changed log (Open -> In Progress) $statusActivity = Activity::where('subject_id', $ticket->id) ->where('event', 'status_changed') ->first(); expect($statusActivity)->not->toBeNull() ->and($statusActivity->properties['old_status'])->toBe(TicketStatusEnum::Open->value) ->and($statusActivity->properties['new_status'])->toBe(TicketStatusEnum::InProgress->value) ->and($statusActivity->description)->toContain('Status updated from open to in-progress'); }); test('getting/opening a ticket logs ticket_opened activity', function (): void { $user = User::factory()->create(); $user->assignRole('user'); $dto = new CreateTicketRequest( userId: $user->id, connectedAppId: null, issueCategory: 'Technical Issue', description: 'SSO login error.', notifiedRoleIds: [], notifiedUserIds: [], attachment: null, ); $service = app(TicketService::class); $ticketData = $service->create($dto); $this->actingAs($user); // Retrieve ticket details $service->getTicket($ticketData->id); // Verify ticket opened activity log $activity = Activity::where('subject_id', $ticketData->id) ->where('event', 'ticket_opened') ->first(); expect($activity)->not->toBeNull() ->and($activity->causer_id)->toBe($user->id) ->and($activity->description)->toContain('opened by'); }); test('notifying users logs user_notified activities', function (): void { Notification::fake(); $user = User::factory()->create(); $user->assignRole('user'); $admin = User::where('email', 'admin@example.com')->firstOrFail(); $this->actingAs($user); $dto = new CreateTicketRequest( userId: $user->id, connectedAppId: null, issueCategory: 'Technical Issue', description: 'SSO login error.', notifiedRoleIds: [], notifiedUserIds: [$admin->id], attachment: null, ); $service = app(TicketService::class); $service->create($dto); // Verify user notified activity logs $activities = Activity::where('subject_id', Ticket::latest()->first()->id) ->where('event', 'user_notified') ->get(); // Should log notifications for the specifically notified admin user and all seeded admins expect($activities->count())->toBeGreaterThan(0); $adminNotificationLog = $activities->first(fn ($act) => $act->properties['recipient_id'] === $admin->id); expect($adminNotificationLog)->not->toBeNull() ->and($adminNotificationLog->properties['recipient_name'])->toBe($admin->name) ->and($adminNotificationLog->description)->toContain('Notified user'); }); test('status changes trigger status changed notification to the user', function (): void { Notification::fake(); $user = User::factory()->create(); $user->assignRole('user'); $admin = User::where('email', 'admin@example.com')->firstOrFail(); $dto = new CreateTicketRequest( userId: $user->id, connectedAppId: null, issueCategory: 'Technical Issue', description: 'SSO login error.', notifiedRoleIds: [], notifiedUserIds: [], attachment: null, ); $service = app(TicketService::class); $ticketData = $service->create($dto); $ticket = Ticket::find($ticketData->id); // Act as admin to change status $this->actingAs($admin); $service->updateStatus($ticket->id, TicketStatusEnum::Hold->value); // The user who raised the ticket should receive the status changed notification Notification::assertSentTo( $user, TicketStatusChangedNotification::class ); });