clusev/tests/Feature/SmtpConfigTest.php

254 lines
10 KiB
PHP

<?php
namespace Tests\Feature;
use App\Livewire\Settings\Email;
use App\Models\AuditEvent;
use App\Models\Setting;
use App\Models\User;
use App\Providers\AppServiceProvider;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Mail;
use Livewire\Livewire;
use Tests\TestCase;
class SmtpConfigTest extends TestCase
{
use RefreshDatabase;
private function actingUser(): User
{
$user = User::factory()->create(['email' => 'admin@clusev.local']);
$this->actingAs($user);
return $user;
}
public function test_saving_persists_settings_and_stores_password_encrypted(): void
{
$this->actingUser();
Livewire::test(Email::class)
->set('mail_host', 'smtp.example.com')
->set('mail_port', 2525)
->set('mail_username', 'panel')
->set('mail_password', 's3cr3t-pw')
->set('mail_encryption', 'tls')
->set('mail_from_address', 'panel@example.com')
->set('mail_from_name', 'Clusev')
->call('save')
->assertHasNoErrors();
$this->assertSame('smtp.example.com', Setting::get('mail_host'));
$this->assertSame('2525', Setting::get('mail_port'));
$this->assertSame('panel', Setting::get('mail_username'));
$this->assertSame('tls', Setting::get('mail_encryption'));
$this->assertSame('panel@example.com', Setting::get('mail_from_address'));
$this->assertSame('Clusev', Setting::get('mail_from_name'));
// Stored value must NOT be the plaintext, but must decrypt back to it.
$stored = Setting::get('mail_password');
$this->assertNotNull($stored);
$this->assertNotSame('s3cr3t-pw', $stored, 'password must be encrypted at rest');
$this->assertSame('s3cr3t-pw', Crypt::decryptString($stored));
$this->assertDatabaseHas('audit_events', ['action' => 'mail.configure', 'target' => 'smtp.example.com']);
}
public function test_clear_config_removes_all_mail_settings(): void
{
$this->actingUser();
Setting::put('mail_host', 'smtp.example.com');
Setting::put('mail_port', '2525');
Setting::put('mail_username', 'panel');
Setting::put('mail_password', Crypt::encryptString('s3cr3t-pw'));
Setting::put('mail_encryption', 'ssl');
Setting::put('mail_from_address', 'panel@example.com');
Setting::put('mail_from_name', 'Clusev');
Livewire::test(Email::class)
->assertSet('mail_host', 'smtp.example.com') // mounted from the stored value
->call('clearConfig')
->assertHasNoErrors()
->assertSet('mail_host', '')
->assertSet('mail_from_address', '')
->assertSet('passwordSet', false);
foreach (['mail_host', 'mail_port', 'mail_username', 'mail_password', 'mail_encryption', 'mail_from_address', 'mail_from_name'] as $key) {
$this->assertNull(Setting::get($key), "$key must be cleared");
}
$this->assertDatabaseHas('audit_events', ['action' => 'mail.reset']);
}
public function test_password_is_not_overwritten_when_left_blank(): void
{
$this->actingUser();
Setting::put('mail_host', 'smtp.example.com');
Setting::put('mail_from_address', 'panel@example.com');
Setting::put('mail_password', Crypt::encryptString('original-pw'));
Livewire::test(Email::class)
->set('mail_from_name', 'Clusev')
->set('mail_password', '') // left blank -> keep the stored one
->call('save')
->assertHasNoErrors();
$this->assertSame('original-pw', Crypt::decryptString(Setting::get('mail_password')));
}
public function test_changing_host_with_a_blank_password_drops_the_stored_password(): void
{
$this->actingUser();
Setting::put('mail_host', 'smtp.old.example.com');
Setting::put('mail_from_address', 'panel@example.com');
Setting::put('mail_password', Crypt::encryptString('original-pw'));
// Retarget the SMTP host but leave the password blank → the old credential must NOT be kept
// (it could otherwise be sent to the new, possibly hostile, server).
Livewire::test(Email::class)
->set('mail_from_name', 'Clusev')
->set('mail_host', 'smtp.attacker.example.com')
->set('mail_password', '')
->call('save')
->assertHasNoErrors()
->assertSet('passwordSet', false);
$this->assertNull(Setting::get('mail_password'));
$this->assertSame('smtp.attacker.example.com', Setting::get('mail_host'));
}
public function test_configured_reflects_host_and_from_presence(): void
{
$this->actingUser();
$component = Livewire::test(Email::class);
$this->assertFalse($component->instance()->configured());
$component->set('mail_host', 'smtp.example.com');
$this->assertFalse($component->instance()->configured(), 'host alone is not enough');
$component->set('mail_from_address', 'panel@example.com');
$this->assertTrue($component->instance()->configured());
}
public function test_runtime_override_switches_to_smtp_when_configured(): void
{
config(['mail.default' => 'log']);
Setting::put('mail_host', 'smtp.example.com');
Setting::put('mail_port', '2525');
Setting::put('mail_username', 'panel');
Setting::put('mail_password', Crypt::encryptString('s3cr3t-pw'));
Setting::put('mail_encryption', 'ssl');
Setting::put('mail_from_address', 'panel@example.com');
Setting::put('mail_from_name', 'Clusev');
// Re-run the provider's boot, which applies the mail settings.
$this->app->register(AppServiceProvider::class, true);
$this->assertSame('smtp', config('mail.default'));
$this->assertSame('smtp.example.com', config('mail.mailers.smtp.host'));
$this->assertSame(2525, config('mail.mailers.smtp.port'));
$this->assertSame('panel', config('mail.mailers.smtp.username'));
$this->assertSame('s3cr3t-pw', config('mail.mailers.smtp.password'));
$this->assertSame('ssl', config('mail.mailers.smtp.encryption'));
$this->assertSame('panel@example.com', config('mail.from.address'));
}
public function test_runtime_override_leaves_default_mailer_when_unconfigured(): void
{
config(['mail.default' => 'log']);
// No settings -> default mailer must stay untouched.
$this->app->register(AppServiceProvider::class, true);
$this->assertSame('log', config('mail.default'));
}
public function test_encryption_none_maps_to_null(): void
{
config(['mail.default' => 'log']);
Setting::put('mail_host', 'smtp.example.com');
Setting::put('mail_encryption', 'none');
Setting::put('mail_from_address', 'panel@example.com');
$this->app->register(AppServiceProvider::class, true);
$this->assertSame('smtp', config('mail.default'));
$this->assertNull(config('mail.mailers.smtp.encryption'));
}
public function test_test_send_dispatches_a_mail_to_the_current_user(): void
{
// Force the smtp mailer onto the in-memory `array` transport so the test-send
// captures the message WITHOUT opening a real SMTP connection. The component's
// runtime config() only sets host/port/etc., never `transport`, so this sticks
// across its Mail::purge('smtp') rebuild — fully deterministic, no network.
config(['mail.mailers.smtp.transport' => 'array']);
$user = $this->actingUser();
Setting::put('mail_host', 'smtp.example.com');
Setting::put('mail_from_address', 'panel@example.com');
Livewire::test(Email::class)
->set('mail_host', 'smtp.example.com')
->set('mail_from_address', 'panel@example.com')
->call('sendTest')
->assertHasNoErrors();
$messages = app('mailer')->getSymfonyTransport()->messages();
$this->assertCount(1, $messages, 'exactly one test mail must be sent');
$sent = $messages[0]->getOriginalMessage();
$recipients = array_map(fn ($a) => $a->getAddress(), $sent->getTo());
$this->assertSame([$user->email], $recipients, 'test mail must go only to the current user');
}
public function test_test_send_refuses_a_changed_host_with_a_blank_password(): void
{
config(['mail.mailers.smtp.transport' => 'array']);
$this->actingUser();
Setting::put('mail_host', 'smtp.old.example.com');
Setting::put('mail_from_address', 'panel@example.com');
Setting::put('mail_password', Crypt::encryptString('stored-pw'));
// Point the form at a NEW host, leave the password blank → the stored password must NOT be
// test-sent to the new (possibly hostile) server; the test is refused.
Livewire::test(Email::class)
->set('mail_host', 'smtp.attacker.example.com')
->set('mail_from_address', 'panel@example.com')
->set('mail_password', '')
->call('sendTest')
->assertHasNoErrors();
$this->assertCount(0, app('mailer')->getSymfonyTransport()->messages(), 'no mail may be sent to a changed host with a blank password');
}
public function test_test_send_does_nothing_when_unconfigured(): void
{
Mail::fake();
$this->actingUser();
Livewire::test(Email::class)
->call('sendTest')
->assertHasNoErrors();
Mail::assertNothingSent();
}
public function test_audit_event_is_recorded_on_save(): void
{
$this->actingUser();
Livewire::test(Email::class)
->set('mail_host', 'smtp.example.com')
->set('mail_from_address', 'panel@example.com')
->set('mail_from_name', 'Clusev')
->call('save')
->assertHasNoErrors();
$this->assertSame(1, AuditEvent::where('action', 'mail.configure')->count());
}
}