create(['name' => 'Delegacje']); $ticket = app(TicketService::class)->create([ 'email' => 'gosc@example.com', 'category_id' => $category->id, 'subject' => 'Sprawa delegacji', 'body' => 'Treść', ], null); expect($ticket->category_id)->toBe($category->id) ->and($ticket->subcategory_id)->toBeNull() ->and($ticket->categoryLabel())->toBe('Delegacje'); }); test('create() leaves category_id null when a subcategory is given (category is derived from it)', function () { seedStatusesAndPriorities(); $subcategory = subcategoryFixture(); $ticket = app(TicketService::class)->create([ 'email' => 'gosc@example.com', 'subcategory_id' => $subcategory->id, 'subject' => 'Sprawa VPN', 'body' => 'Treść', ], null); expect($ticket->category_id)->toBeNull() ->and($ticket->categoryLabel())->toBe('IT / VPN'); }); test('create() defaults source to web, and accepts an explicit source', function () { seedStatusesAndPriorities(); $webTicket = app(TicketService::class)->create([ 'email' => 'a@example.com', 'subject' => 'S', 'body' => 'B', ], null); $mailTicket = app(TicketService::class)->create([ 'email' => 'b@example.com', 'subject' => 'S', 'body' => 'B', 'source' => 'email', ], null); expect($webTicket->source)->toBe('web') ->and($mailTicket->source)->toBe('email'); }); // ===================== ImapMailbox::targetLabel() ===================== test('targetLabel reflects subcategory, whole-category, or neither', function () { $subcategory = subcategoryFixture(); $category = Category::query()->create(['name' => 'Delegacje']); $bySubcategory = ImapMailbox::query()->create(mailboxFixtureData(['default_subcategory_id' => $subcategory->id])); $byCategory = ImapMailbox::query()->create(mailboxFixtureData(['default_category_id' => $category->id])); $unrouted = ImapMailbox::query()->create(mailboxFixtureData()); expect($bySubcategory->targetLabel())->toBe('IT / VPN') ->and($byCategory->targetLabel())->toBe('Cała kategoria: Delegacje') ->and($unrouted->targetLabel())->toBe('—'); }); function mailboxFixtureData(array $overrides = []): array { return array_merge([ 'name' => 'Test', 'enabled' => true, 'host' => 'imap.example.com', 'port' => 993, 'encryption' => 'ssl', 'validate_cert' => true, 'username' => 'test@example.com', 'password' => 'secret', 'folder' => 'INBOX', ], $overrides); } // ===================== Admin: MailSettings mailbox form ===================== test('admin can route a mailbox to a whole category via the combined selector', function () { $admin = adminUser(); $category = Category::query()->create(['name' => 'Delegacje']); Livewire::actingAs($admin)->test(MailSettings::class) ->call('openMailboxForm') ->set('mailboxForm.name', 'Zgłoszenia delegacji') ->set('mailboxForm.host', 'imap.example.com') ->set('mailboxForm.username', 'zgloszenia-delegacje@example.com') ->set('mailboxForm.password', 'secret') ->set('mailboxForm.target', "category:{$category->id}") ->call('submitMailboxForm') ->assertOk(); $mailbox = ImapMailbox::query()->where('name', 'Zgłoszenia delegacji')->firstOrFail(); expect($mailbox->default_category_id)->toBe($category->id) ->and($mailbox->default_subcategory_id)->toBeNull(); }); test('admin can route a mailbox to a specific subcategory via the combined selector', function () { $admin = adminUser(); $subcategory = subcategoryFixture(); Livewire::actingAs($admin)->test(MailSettings::class) ->call('openMailboxForm') ->set('mailboxForm.name', 'Zgłoszenia IT') ->set('mailboxForm.host', 'imap.example.com') ->set('mailboxForm.username', 'zgloszenia-it@example.com') ->set('mailboxForm.password', 'secret') ->set('mailboxForm.target', "subcategory:{$subcategory->id}") ->call('submitMailboxForm') ->assertOk(); $mailbox = ImapMailbox::query()->where('name', 'Zgłoszenia IT')->firstOrFail(); expect($mailbox->default_subcategory_id)->toBe($subcategory->id) ->and($mailbox->default_category_id)->toBeNull(); }); test('switching an existing mailbox from a subcategory to a whole category clears the old target', function () { $admin = adminUser(); $subcategory = subcategoryFixture(); $category = Category::query()->create(['name' => 'Delegacje']); $mailbox = ImapMailbox::query()->create(mailboxFixtureData(['default_subcategory_id' => $subcategory->id])); Livewire::actingAs($admin)->test(MailSettings::class) ->call('editMailbox', $mailbox->id) ->assertSet('mailboxForm.target', "subcategory:{$subcategory->id}") ->set('mailboxForm.target', "category:{$category->id}") ->call('submitMailboxForm') ->assertOk(); $mailbox->refresh(); expect($mailbox->default_category_id)->toBe($category->id) ->and($mailbox->default_subcategory_id)->toBeNull(); }); // ===================== Operator UI: e-mail source badge ===================== test('the operator queue shows a mail icon next to an e-mail-originated ticket but not a web one', function () { seedStatusesAndPriorities(); $operator = operatorUser(); $webTicket = makeTicket(['number' => '2001', 'source' => 'web']); $mailTicket = makeTicket(['number' => '2002', 'source' => 'email']); Livewire::actingAs($operator)->test(Queue::class) ->assertSeeHtml('title="Utworzone przez e-mail"'); }); test('the ticket detail header shows an e-mail badge only for e-mail-originated tickets', function () { seedStatusesAndPriorities(); $operator = operatorUser(); $mailTicket = makeTicket(['number' => '2003', 'source' => 'email']); $webTicket = makeTicket(['number' => '2004', 'source' => 'web']); Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $mailTicket]) ->assertSee('E-mail'); Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $webTicket]) ->assertDontSee('E-mail'); }); // ===================== Operator queue: category-only tickets are filterable ===================== test('filtering the queue by category includes a ticket routed to that whole category with no subcategory', function () { seedStatusesAndPriorities(); $operator = operatorUser(); $category = Category::query()->create(['name' => 'Delegacje']); $ticket = makeTicket(['number' => '2005', 'category_id' => $category->id]); Livewire::actingAs($operator)->test(Queue::class) ->set('filterCategory', $category->id) ->assertSee($ticket->displayNumber()); }); // ===================== Operator UI: per-message e-mail source badge ===================== test('a reply fetched by e-mail shows a mail badge in the thread, a normal client reply does not', function () { seedStatusesAndPriorities(); $operator = operatorUser(); $client = User::query()->create(['name' => 'Klient', 'email' => 'klient@example.com', 'roles' => ['client']]); $ticket = makeTicket(['number' => '2006']); app(TicketService::class)->clientReply($ticket, $client, 'Odpowiedź z portalu.'); app(TicketService::class)->clientReply($ticket, $client, 'Odpowiedź e-mailem.', source: 'email'); Livewire::actingAs($operator)->test(TicketShow::class, ['ticket' => $ticket]) ->assertSeeHtml('title="Odebrane e-mailem"'); });