singleloginsystem/tests/Feature/TicketActivityLogTest.php
= 8a41ae6ac1 Feat: assigned user status change, search, pagination and notification
- Added activity logs for ticket status changes, comments, and ticket views.
- Implemented `TicketStatusChangedNotification` to notify users of status updates.
- Enhanced ticket list filtering with pagination, search, and status filters.
- Updated UI to better handle ticket assignments, status updates, and comment notifications.
- Extended tests for activity logs, unauthorized actions, status change notifications, and filtering functionalities.
2026-05-28 13:22:36 +00:00

245 lines
7.7 KiB
PHP

<?php
declare(strict_types=1);
use App\Data\Ticket\{CreateTicketRequest, PostReplyRequest};
use App\Enums\TicketStatusEnum;
use App\Models\{Ticket, User};
use App\Notifications\TicketStatusChangedNotification;
use App\Services\TicketService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Support\Facades\Notification;
use Spatie\Activitylog\Models\Activity;
beforeEach(function (): void {
$this->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
);
});