seedLookupData(); $owner = $this->user(); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); Livewire::actingAs($owner) ->test(TicketShow::class, ['ticket' => $ticket]) ->set('commentBody', '

Please can you check this update?

') ->call('addComment') ->assertHasNoErrors() ->assertDispatched('helpdesk-comment-added'); $this->assertDatabaseHas('helpdesk_comments', [ 'ticket_id' => $ticket->id, 'parent_id' => null, 'user_id' => $owner->id, 'is_internal' => false, ]); } public function test_comment_can_have_a_nested_reply(): void { $fixtures = $this->seedLookupData(); $owner = $this->user(); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); $comment = Comment::query()->create([ 'ticket_id' => $ticket->id, 'user_id' => $owner->id, 'body' => '

Parent response

', 'is_internal' => false, ]); Livewire::actingAs($owner) ->test(TicketShow::class, ['ticket' => $ticket]) ->call('startReply', $comment->id) ->set('replyBody', '

Nested reply body

') ->call('addReply') ->assertHasNoErrors() ->assertDispatched('helpdesk-reply-added'); $this->assertDatabaseHas('helpdesk_comments', [ 'ticket_id' => $ticket->id, 'parent_id' => $comment->id, 'body' => '

Nested reply body

', ]); } public function test_comment_post_endpoint_creates_lotus_identity_and_search_text(): void { $fixtures = $this->seedLookupData(); $owner = $this->user(); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); $this->actingAs($owner) ->post(route('helpdesk.tickets.comments.store', $ticket), [ 'subject' => 'Searchable response subject', 'commentBody' => '

Searchable response

', ]) ->assertRedirect(route('helpdesk.tickets.show', $ticket)); $comment = Comment::query()->where('ticket_id', $ticket->id)->firstOrFail(); self::assertNotEmpty($comment->guid); self::assertNotEmpty($comment->unid); self::assertSame($comment->unid, $comment->root_unid); self::assertSame('Searchable response subject', $comment->subject); self::assertSame('Searchable response', $comment->body_text); self::assertSame(1, $comment->sync_sequence); } public function test_nested_reply_post_endpoint_preserves_lotus_parent_identity(): void { $fixtures = $this->seedLookupData(); $owner = $this->user(); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); $parent = app(TicketService::class)->comment($ticket, $owner, '

Parent response

'); $this->actingAs($owner) ->post(route('helpdesk.tickets.comments.store', $ticket), [ 'parent_id' => $parent->id, 'subject' => 'RE: Parent response', 'commentBody' => '

Child response

', ]) ->assertRedirect(route('helpdesk.tickets.show', $ticket)); $reply = Comment::query()->where('parent_id', $parent->id)->firstOrFail(); self::assertSame($parent->unid, $reply->parent_unid); self::assertSame($parent->root_unid, $reply->root_unid); self::assertSame('RE: Parent response', $reply->subject); } public function test_existing_ticket_accepts_multiple_supporting_file_attachments(): void { Storage::fake((string) config('helpdesk.disk', 'public')); $fixtures = $this->seedLookupData(); $owner = $this->user(); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); Livewire::actingAs($owner) ->test(TicketShow::class, ['ticket' => $ticket]) ->set('supportingFiles', [ UploadedFile::fake()->create('existing-ticket-log.txt', 8, 'text/plain'), UploadedFile::fake()->create('existing-ticket-proof.pdf', 12, 'application/pdf'), ]) ->call('saveSupportingFiles') ->assertHasNoErrors() ->assertDispatched('helpdesk-supporting-files-added'); self::assertSame(2, Attachment::query()->where('ticket_id', $ticket->id)->whereNull('comment_id')->count()); $this->assertDatabaseHas('helpdesk_attachments', [ 'ticket_id' => $ticket->id, 'comment_id' => null, 'original_name' => 'existing-ticket-log.txt', ]); $this->assertDatabaseHas('helpdesk_attachments', [ 'ticket_id' => $ticket->id, 'comment_id' => null, 'original_name' => 'existing-ticket-proof.pdf', ]); } public function test_post_endpoint_accepts_multiple_comment_attachments(): void { Storage::fake((string) config('helpdesk.disk', 'public')); $fixtures = $this->seedLookupData(); $owner = $this->user(); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); $this->actingAs($owner) ->post(route('helpdesk.tickets.comments.store', $ticket), [ 'subject' => 'Attachments attached', 'commentBody' => '

Files attached.

', 'attachments' => [ UploadedFile::fake()->create('first-log.txt', 8, 'text/plain'), UploadedFile::fake()->create('second-log.txt', 8, 'text/plain'), ], ]) ->assertRedirect(route('helpdesk.tickets.show', $ticket)); $comment = Comment::query()->where('ticket_id', $ticket->id)->firstOrFail(); self::assertSame(2, Attachment::query()->where('comment_id', $comment->id)->count()); } public function test_mpba_platform_admin_can_delete_comment(): void { $fixtures = $this->seedLookupData(); $company = Company::query()->create([ 'name' => 'MPBA Limited', 'slug' => 'mpba-limited', 'status' => 'active', 'timezone' => 'Europe/London', 'is_system_company' => true, ]); $admin = $this->user(['company_id' => $company->id, 'is_platform_admin' => true]); $owner = $this->user(['company_id' => $company->id]); $ticket = $this->ticket(['owner' => $owner, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); $comment = app(TicketService::class)->comment($ticket, $owner, '

Delete me

'); $this->actingAs($admin) ->delete(route('helpdesk.tickets.comments.destroy', [$ticket, $comment])) ->assertRedirect(route('helpdesk.tickets.show', $ticket)); $comment->refresh(); self::assertNotNull($comment->tombstoned_at); } public function test_agent_can_update_status_to_closed_and_priority(): void { Mail::fake(); $fixtures = $this->seedLookupData(); $owner = $this->user(); $agent = $this->agent(); $ticket = $this->ticket(['owner' => $owner, 'agent' => $agent, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); Livewire::actingAs($agent) ->test(TicketShow::class, ['ticket' => $ticket]) ->set('status_id', $fixtures['closed']->id) ->set('closureComment', 'Closing after agent review.') ->call('updateStatus') ->assertHasNoErrors() ->assertDispatched('helpdesk-status-updated') ->set('priority_id', $fixtures['high']->id) ->call('updatePriority') ->assertHasNoErrors() ->assertDispatched('helpdesk-priority-updated'); $ticket->refresh(); self::assertNotNull($ticket->completed_at); self::assertSame($fixtures['closed']->id, $ticket->status_id); self::assertSame($fixtures['high']->id, $ticket->priority_id); } public function test_closing_ticket_requires_a_closure_comment(): void { $fixtures = $this->seedLookupData(); $owner = $this->user(); $agent = $this->agent(); $ticket = $this->ticket([ 'owner' => $owner, 'agent' => $agent, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id, ]); $this->actingAs($agent) ->from(route('helpdesk.tickets.show', $ticket)) ->post(route('helpdesk.tickets.status.update', $ticket), [ 'status_id' => $fixtures['closed']->id, ]) ->assertRedirect(route('helpdesk.tickets.show', $ticket)) ->assertSessionHasErrors('closure_comment'); $ticket->refresh(); self::assertSame($fixtures['open']->id, $ticket->status_id); self::assertNull($ticket->completed_at); } public function test_closing_ticket_creates_a_closure_comment(): void { $fixtures = $this->seedLookupData(); $owner = $this->user(); $agent = $this->agent(); $ticket = $this->ticket([ 'owner' => $owner, 'agent' => $agent, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id, ]); $this->actingAs($agent) ->post(route('helpdesk.tickets.status.update', $ticket), [ 'status_id' => $fixtures['closed']->id, 'closure_comment' => 'Issue resolved and verified before closure.', 'closure_internal' => '0', ]) ->assertRedirect(route('helpdesk.tickets.show', $ticket)); $ticket->refresh(); self::assertSame($fixtures['closed']->id, $ticket->status_id); self::assertNotNull($ticket->completed_at); $this->assertDatabaseHas('helpdesk_comments', [ 'ticket_id' => $ticket->id, 'user_id' => $agent->id, 'subject' => 'Closure comment', 'body' => 'Issue resolved and verified before closure.', 'is_internal' => false, ]); } public function test_unassigned_agent_cannot_update_another_users_ticket(): void { $fixtures = $this->seedLookupData(); $owner = $this->user(); $agent = $this->agent(); $ticket = $this->ticket(['owner' => $owner, 'agent_id' => null, 'status_id' => $fixtures['open']->id, 'priority_id' => $fixtures['normal']->id]); Livewire::actingAs($agent) ->test(TicketShow::class, ['ticket' => $ticket]) ->assertForbidden(); } }