From 7b40a55b0e83426469560f0a048ca484dd39ad5b Mon Sep 17 00:00:00 2001 From: Mark Gregory Date: Fri, 26 Jun 2026 15:59:12 +0100 Subject: [PATCH] create issues with attachmens --- ...pdeskCreateTicketAttachmentBrowserTest.php | 220 ++++++++++++++++++ ...icketCreateAttachmentBrowserSafetyTest.php | 52 +++++ .../TicketCreateAttachmentPipelineTest.php | 143 ++++++++++++ 3 files changed, 415 insertions(+) create mode 100644 Modules/Helpdesk/Tests/Browser/HelpdeskCreateTicketAttachmentBrowserTest.php create mode 100644 Modules/Helpdesk/Tests/Feature/TicketCreateAttachmentBrowserSafetyTest.php create mode 100644 Modules/Helpdesk/Tests/Feature/TicketCreateAttachmentPipelineTest.php diff --git a/Modules/Helpdesk/Tests/Browser/HelpdeskCreateTicketAttachmentBrowserTest.php b/Modules/Helpdesk/Tests/Browser/HelpdeskCreateTicketAttachmentBrowserTest.php new file mode 100644 index 00000000..e4d6a907 --- /dev/null +++ b/Modules/Helpdesk/Tests/Browser/HelpdeskCreateTicketAttachmentBrowserTest.php @@ -0,0 +1,220 @@ +seedLookupData(); + $user = $this->user(['name' => 'Browser Attachment User']); + $subject = 'Browser attachment ticket ' . uniqid('', true); + $filePath = $this->makeBrowserUploadFile('browser-upload-proof.log', 'Browser upload proof file.'); + + $this->browse(function (Browser $browser) use ($user, $fixtures, $subject, $filePath): void { + $browser->loginAs($user) + ->visitRoute('helpdesk.tickets.create') + ->waitFor('@helpdesk-ticket-create', 10) + ->assertSee('Create ticket') + ->type('@ticket-subject', $subject); + + $this->setHelpdeskEditor( + $browser, + 'ticket-body-editor', + 'This browser ticket proves attachments can be uploaded and saved.', + ); + + $this->setHelpdeskSelect($browser, 'board_id', $fixtures['board']->id); + $this->setHelpdeskSelect($browser, 'priority_id', $fixtures['high']->id); + $this->setHelpdeskSelect($browser, 'category_id', $fixtures['category']->id); + $this->setHelpdeskSelect($browser, 'team_id', $fixtures['team']->id); + $this->setHelpdeskSelect($browser, 'project_id', $fixtures['project']->id); + + $browser->attach('#attachments', $filePath); + + $this->waitForHelpdeskUploadToFinish($browser); + + $browser->press('@ticket-create-submit') + ->waitUntilMissing('@helpdesk-ticket-create', 20) + ->assertPathIs('/helpdesk/tickets') + ->assertSee($subject); + }); + + $ticket = Ticket::query() + ->where('subject', $subject) + ->firstOrFail(); + + $attachment = Attachment::query() + ->where('ticket_id', $ticket->id) + ->whereNull('comment_id') + ->where('original_name', 'browser-upload-proof.log') + ->firstOrFail(); + + self::assertSame($ticket->id, (int) $attachment->ticket_id); + } + + public function test_user_can_create_ticket_with_multiple_attachments_in_browser(): void + { + $fixtures = $this->seedLookupData(); + $user = $this->user(['name' => 'Browser Multi Attachment User']); + $subject = 'Browser multiple attachment ticket ' . uniqid('', true); + $firstFile = $this->makeBrowserUploadFile('browser-first-proof.txt', 'First browser upload proof file.'); + $secondFile = $this->makeBrowserUploadFile('browser-second-proof.csv', 'name,value' . PHP_EOL . 'proof,1'); + + $this->browse(function (Browser $browser) use ($user, $fixtures, $subject, $firstFile, $secondFile): void { + $browser->loginAs($user) + ->visitRoute('helpdesk.tickets.create') + ->waitFor('@helpdesk-ticket-create', 10) + ->assertSee('Create ticket') + ->type('@ticket-subject', $subject); + + $this->setHelpdeskEditor( + $browser, + 'ticket-body-editor', + 'This browser ticket proves multiple attachments can be uploaded and saved.', + ); + + $this->setHelpdeskSelect($browser, 'board_id', $fixtures['board']->id); + $this->setHelpdeskSelect($browser, 'priority_id', $fixtures['normal']->id); + $this->setHelpdeskSelect($browser, 'category_id', $fixtures['category']->id); + $this->setHelpdeskSelect($browser, 'team_id', $fixtures['team']->id); + $this->setHelpdeskSelect($browser, 'project_id', $fixtures['project']->id); + + $browser->attach('#attachments', $firstFile . "\n" . $secondFile); + + $this->waitForHelpdeskUploadToFinish($browser); + + $browser->press('@ticket-create-submit') + ->waitUntilMissing('@helpdesk-ticket-create', 20) + ->assertPathIs('/helpdesk/tickets') + ->assertSee($subject); + }); + + $ticket = Ticket::query() + ->where('subject', $subject) + ->firstOrFail(); + + self::assertSame( + 2, + Attachment::query()->where('ticket_id', $ticket->id)->whereNull('comment_id')->count(), + ); + + foreach (['browser-first-proof.txt', 'browser-second-proof.csv'] as $name) { + $this->assertDatabaseHas('helpdesk_attachments', [ + 'ticket_id' => $ticket->id, + 'comment_id' => null, + 'original_name' => $name, + ]); + } + } + + private function makeBrowserUploadFile(string $name, string $contents): string + { + $directory = storage_path('framework/testing/helpdesk-browser-uploads'); + + if (! is_dir($directory)) { + mkdir($directory, 0777, true); + } + + $path = $directory . DIRECTORY_SEPARATOR . $name; + file_put_contents($path, $contents); + + return $path; + } + + private function setHelpdeskEditor(Browser $browser, string $dusk, string $text): void + { + $encodedDusk = json_encode($dusk, JSON_THROW_ON_ERROR); + $encodedText = json_encode($text, JSON_THROW_ON_ERROR); + + $browser->script(<<pause(500); + } + + private function setHelpdeskSelect(Browser $browser, string $model, int|string|null $value): void + { + $encodedModel = json_encode($model, JSON_THROW_ON_ERROR); + $encodedValue = json_encode($value === null ? '' : (string) $value, JSON_THROW_ON_ERROR); + + $browser->script(<<pause(250); + } + + private function waitForHelpdeskUploadToFinish(Browser $browser): void + { + $browser->waitUsing(30, 250, static function () use ($browser): bool { + return (bool) $browser->script(<<<'JS' +const input = document.querySelector('#attachments'); +const button = document.querySelector('[dusk="ticket-create-submit"], [data-dusk="ticket-create-submit"]'); + +const isVisible = (element) => { + if (! element) { + return false; + } + + const style = window.getComputedStyle(element); + + return style.display !== 'none' + && style.visibility !== 'hidden' + && style.opacity !== '0' + && element.getClientRects().length > 0; +}; + +const visibleUploadText = Array.from(document.querySelectorAll('div, span')) + .some((element) => { + const text = (element.textContent || '').trim(); + + return (text.includes('Uploading attachments...') || text === 'Uploading...') + && isVisible(element); + }); + +return Boolean(input) + && input.files.length > 0 + && Boolean(button) + && button.disabled === false + && visibleUploadText === false; +JS)[0]; + }, 'The attachment upload did not finish before submit.'); + } +} diff --git a/Modules/Helpdesk/Tests/Feature/TicketCreateAttachmentBrowserSafetyTest.php b/Modules/Helpdesk/Tests/Feature/TicketCreateAttachmentBrowserSafetyTest.php new file mode 100644 index 00000000..9a74ca82 --- /dev/null +++ b/Modules/Helpdesk/Tests/Feature/TicketCreateAttachmentBrowserSafetyTest.php @@ -0,0 +1,52 @@ +set('helpdesk.disk', 'local'); + + $fixtures = $this->seedLookupData(); + $user = $this->user(['name' => 'Attachment Create User']); + $file = UploadedFile::fake()->create('warehouse-error.log', 12, 'text/plain'); + + Livewire::actingAs($user) + ->test(TicketCreate::class) + ->set('subject', 'Create ticket with one attachment') + ->set('body', '

This ticket must persist a Livewire upload attachment.

') + ->set('board_id', $fixtures['board']->id) + ->set('priority_id', $fixtures['high']->id) + ->set('category_id', $fixtures['category']->id) + ->set('team_id', $fixtures['team']->id) + ->set('project_id', $fixtures['project']->id) + ->set('attachments', [$file]) + ->call('save') + ->assertHasNoErrors() + ->assertRedirect(route('helpdesk.tickets.index')); + + $ticket = Ticket::query() + ->where('subject', 'Create ticket with one attachment') + ->firstOrFail(); + + $attachment = Attachment::query() + ->where('ticket_id', $ticket->id) + ->whereNull('comment_id') + ->firstOrFail(); + + self::assertSame($user->id, (int) $attachment->user_id); + self::assertSame('local', $attachment->disk); + self::assertSame('warehouse-error.log', $attachment->original_name); + self::assertSame('log', $attachment->extension()); + Storage::disk('local')->assertExists($attachment->path); + } + + public function test_ticket_create_persists_multiple_attachments_through_livewire(): void + { + Storage::fake('local'); + config()->set('helpdesk.disk', 'local'); + + $fixtures = $this->seedLookupData(); + $user = $this->user(['name' => 'Multiple Attachment User']); + + Livewire::actingAs($user) + ->test(TicketCreate::class) + ->set('subject', 'Create ticket with multiple attachments') + ->set('body', '

This ticket must persist every selected Livewire upload.

') + ->set('board_id', $fixtures['board']->id) + ->set('priority_id', $fixtures['normal']->id) + ->set('category_id', $fixtures['category']->id) + ->set('team_id', $fixtures['team']->id) + ->set('project_id', $fixtures['project']->id) + ->set('attachments', [ + UploadedFile::fake()->create('first-proof.pdf', 20, 'application/pdf'), + UploadedFile::fake()->create('second-proof.txt', 8, 'text/plain'), + UploadedFile::fake()->create('third-proof.csv', 6, 'text/csv'), + ]) + ->call('save') + ->assertHasNoErrors() + ->assertRedirect(route('helpdesk.tickets.index')); + + $ticket = Ticket::query() + ->where('subject', 'Create ticket with multiple attachments') + ->firstOrFail(); + + self::assertSame(3, Attachment::query()->where('ticket_id', $ticket->id)->whereNull('comment_id')->count()); + + foreach (['first-proof.pdf', 'second-proof.txt', 'third-proof.csv'] as $name) { + $attachment = Attachment::query() + ->where('ticket_id', $ticket->id) + ->where('original_name', $name) + ->firstOrFail(); + + Storage::disk('local')->assertExists($attachment->path); + } + } + + public function test_ticket_create_accepts_source_code_attachments_through_livewire(): void + { + Storage::fake('local'); + config()->set('helpdesk.disk', 'local'); + + $fixtures = $this->seedLookupData(); + $user = $this->user(['name' => 'Source Attachment User']); + + Livewire::actingAs($user) + ->test(TicketCreate::class) + ->set('subject', 'Create ticket with source attachments') + ->set('body', '

This ticket must accept source files by extension.

') + ->set('board_id', $fixtures['board']->id) + ->set('priority_id', $fixtures['normal']->id) + ->set('category_id', $fixtures['category']->id) + ->set('team_id', $fixtures['team']->id) + ->set('project_id', $fixtures['project']->id) + ->set('attachments', [ + UploadedFile::fake()->create('TicketCreate.php', 10, 'text/plain'), + UploadedFile::fake()->create('upload-widget.js', 10, 'text/plain'), + UploadedFile::fake()->create('warehouse-sync.rpgle', 10, 'text/plain'), + ]) + ->call('save') + ->assertHasNoErrors() + ->assertRedirect(route('helpdesk.tickets.index')); + + $ticket = Ticket::query() + ->where('subject', 'Create ticket with source attachments') + ->firstOrFail(); + + foreach (['TicketCreate.php', 'upload-widget.js', 'warehouse-sync.rpgle'] as $name) { + $attachment = Attachment::query() + ->where('ticket_id', $ticket->id) + ->where('original_name', $name) + ->firstOrFail(); + + self::assertTrue($attachment->isSourceCode(), $name . ' should be categorised as source code.'); + Storage::disk('local')->assertExists($attachment->path); + } + } +}