- 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.
245 lines
7.7 KiB
PHP
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
|
|
);
|
|
});
|