- E-mail intake (IMAP), optional and off by default: clients can create a ticket or reply to an existing one just by sending/replying to an e-mail. Configure any number of mailboxes in the new Admin > Poczta page (SMTP + IMAP together, replacing the old "E-MAIL" tab), each routed to a specific subcategory or a whole category (new tickets.category_id column). Replies are matched to their ticket via the number/checksum already in every notification subject; autoresponders/bounces are detected and rejected; "restrict tickets to LDAP" is enforced for e-mail like the guest web form. Manual "Pobierz teraz" per-mailbox fetch button; dedicated storage/logs/imap-*.log regardless of the app's log level; mail-icon badges on e-mail-originated tickets/messages in the operator queue and ticket view. - Operator queue: "select all" checkbox in the table header for every currently visible ticket under the active filter/tab. - Fixed: scheduled commands (SLA breach check, automation rules, and now IMAP fetch) always sent notifications through .env's default mailer instead of the configured SMTP server, because AppServiceProvider's Settings override used to skip itself for any console command, not just migrate. - Fixed: visiting a ticket that no longer exists (deleted mid-session, or a stale background refresh) showed a raw 404 instead of redirecting back to the operator queue / client dashboard. - Docs: README/ARCHITECTURE/CLAUDE/install/wiki updated for all of the above, including the previously-missing host crontab entry for schedule:run. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
139 lines
5.8 KiB
PHP
139 lines
5.8 KiB
PHP
<?php
|
|
|
|
use App\Livewire\Admin\MailSettings;
|
|
use App\Models\EmailTemplate;
|
|
use App\Notifications\TicketNotification;
|
|
use App\Providers\AppServiceProvider;
|
|
use App\Support\Settings;
|
|
use Illuminate\Support\Facades\Config;
|
|
use Illuminate\Support\Facades\Mail;
|
|
use Livewire\Livewire;
|
|
|
|
test('admin can save the SMTP/from settings, and the password is only overwritten when provided', function () {
|
|
$admin = adminUser();
|
|
|
|
Livewire::actingAs($admin)->test(MailSettings::class)
|
|
->set('mailConfig.fromAddress', 'wsparcie@firma.pl')
|
|
->set('mailConfig.fromName', 'Zespół Wsparcia')
|
|
->set('mailConfig.smtpEnabled', true)
|
|
->set('mailConfig.smtpHost', 'smtp.firma.pl')
|
|
->set('mailConfig.smtpPort', '587')
|
|
->set('mailConfig.smtpUsername', 'no-reply')
|
|
->set('mailConfig.smtpPassword', 'sekret123')
|
|
->set('mailConfig.smtpEncryption', 'tls')
|
|
->call('saveMailConfig')
|
|
->assertOk();
|
|
|
|
expect(Settings::get('mail_from_address'))->toBe('wsparcie@firma.pl')
|
|
->and(Settings::get('mail_from_name'))->toBe('Zespół Wsparcia')
|
|
->and(Settings::bool('mail_smtp_enabled'))->toBeTrue()
|
|
->and(Settings::get('mail_smtp_host'))->toBe('smtp.firma.pl')
|
|
->and(Settings::get('mail_smtp_password'))->toBe('sekret123');
|
|
|
|
// Saving again with a blank password field must not wipe the stored one.
|
|
Livewire::actingAs($admin)->test(MailSettings::class)
|
|
->set('mailConfig.smtpHost', 'smtp.firma.pl')
|
|
->set('mailConfig.smtpPassword', '')
|
|
->call('saveMailConfig')
|
|
->assertOk();
|
|
|
|
expect(Settings::get('mail_smtp_password'))->toBe('sekret123');
|
|
});
|
|
|
|
test('the SMTP test button reports an error without a host/from address, and success once mail is faked and both are set', function () {
|
|
Mail::fake();
|
|
$admin = adminUser();
|
|
|
|
Livewire::actingAs($admin)->test(MailSettings::class)
|
|
->call('testMailConnection')
|
|
->assertSet('mailTestResult', 'error');
|
|
|
|
// Mail::fake()'s raw() is a no-op that never throws, so a valid config
|
|
// reports success — this exercises the same config-override/restore path
|
|
// real sends use, without needing a reachable SMTP server in tests.
|
|
Livewire::actingAs($admin)->test(MailSettings::class)
|
|
->set('mailConfig.fromAddress', 'wsparcie@firma.pl')
|
|
->set('mailConfig.smtpHost', 'smtp.firma.pl')
|
|
->call('testMailConnection')
|
|
->assertSet('mailTestResult', 'ok');
|
|
});
|
|
|
|
test('the configured footer is appended to every rendered notification', function () {
|
|
Settings::set('email_footer', 'Stopka testowa.');
|
|
$template = EmailTemplate::query()->create([
|
|
'key' => 'tpl-footer-test', 'name' => 'x', 'trigger_label' => 'x', 'subject' => 'S', 'body' => 'Treść wiadomości.',
|
|
]);
|
|
$ticket = makeTicket();
|
|
|
|
$mail = (new TicketNotification($ticket, $template->id))->toMail((object) ['routes' => ['mail' => $ticket->email]]);
|
|
|
|
expect($mail->viewData['html'])->toContain('Treść wiadomości.')
|
|
->and($mail->viewData['html'])->toContain('Stopka testowa.');
|
|
});
|
|
|
|
test('an empty footer setting adds nothing extra to the notification', function () {
|
|
Settings::set('email_footer', '');
|
|
$template = EmailTemplate::query()->create([
|
|
'key' => 'tpl-footer-test-2', 'name' => 'x', 'trigger_label' => 'x', 'subject' => 'S', 'body' => 'Treść wiadomości.',
|
|
]);
|
|
$ticket = makeTicket();
|
|
|
|
$mail = (new TicketNotification($ticket, $template->id))->toMail((object) ['routes' => ['mail' => $ticket->email]]);
|
|
|
|
expect($mail->viewData['html'])->toContain('Treść wiadomości.');
|
|
});
|
|
|
|
test('AppServiceProvider overrides the mail config from settings only when SMTP is enabled', function () {
|
|
Settings::set('mail_smtp_enabled', '0');
|
|
Settings::set('mail_smtp_host', 'smtp.disabled.example');
|
|
(new AppServiceProvider(app()))->boot();
|
|
expect(config('mail.default'))->not->toBe('smtp');
|
|
|
|
Settings::set('mail_smtp_enabled', '1');
|
|
Settings::set('mail_smtp_host', 'smtp.enabled.example');
|
|
Settings::set('mail_smtp_port', '2525');
|
|
(new AppServiceProvider(app()))->boot();
|
|
|
|
expect(config('mail.default'))->toBe('smtp')
|
|
->and(config('mail.mailers.smtp.host'))->toBe('smtp.enabled.example')
|
|
->and(config('mail.mailers.smtp.port'))->toBe(2525);
|
|
});
|
|
|
|
test('AppServiceProvider always applies the from-address override regardless of SMTP toggle', function () {
|
|
Settings::set('mail_smtp_enabled', '0');
|
|
Settings::set('mail_from_address', 'wsparcie@firma.pl');
|
|
Settings::set('mail_from_name', 'Wsparcie');
|
|
|
|
(new AppServiceProvider(app()))->boot();
|
|
|
|
expect(config('mail.from.address'))->toBe('wsparcie@firma.pl')
|
|
->and(config('mail.from.name'))->toBe('Wsparcie');
|
|
});
|
|
|
|
test('regression: the mail override still applies for a console command other than migrate (e.g. schedule:run/tinker)', function () {
|
|
// Reproduces the real production bug: settingsTableUsable() used to
|
|
// blanket-skip for *any* console command, which meant scheduled
|
|
// commands (emails:fetch-imap, tickets:check-sla-breaches) always sent
|
|
// mail via the .env "log" mailer instead of the configured SMTP server,
|
|
// since AppServiceProvider::boot() runs on every process including
|
|
// console ones. Only the migrate family should still be excluded.
|
|
$originalArgv = $_SERVER['argv'] ?? null;
|
|
|
|
Settings::set('mail_smtp_enabled', '1');
|
|
Settings::set('mail_smtp_host', 'smtp.enabled.example');
|
|
|
|
try {
|
|
$_SERVER['argv'] = ['artisan', 'emails:fetch-imap'];
|
|
(new AppServiceProvider(app()))->boot();
|
|
expect(config('mail.default'))->toBe('smtp');
|
|
|
|
Config::set('mail.default', 'log');
|
|
|
|
$_SERVER['argv'] = ['artisan', 'migrate'];
|
|
(new AppServiceProvider(app()))->boot();
|
|
expect(config('mail.default'))->not->toBe('smtp');
|
|
} finally {
|
|
$_SERVER['argv'] = $originalArgv;
|
|
}
|
|
});
|