Files
coolify/tests/Unit/SmtpTransportFactoryTest.php
Andras Bacsai f39c884cc7 fix(servers): support IPv6 server addresses everywhere
Store server IPs in one form (no brackets, short lower-case IPv6) and
write each address in the form its context needs:

- URLs and host:port (Sentinel, database URLs, SFTP backup download,
  DNS servers, SMTP, webhook base URLs, links, health checks) get
  brackets through formatHostForUrl().
- Traefik Host() rules use the address without brackets, so IPv6
  domains no longer return 404; saved labels are fixed at deploy.
  Caddy keeps the brackets.
- SSH keeps the plain address.
- Hetzner gives an IPv6 /64 network; IPv6-only servers now get the
  server address (prefix::1) instead of the unusable prefix::64.
- sslip labels, registry image names with an IPv6 host, the API domain
  list, allowlist matching and duplicate checks handle IPv6.

A migration normalizes stored IPv6 server addresses, repairs Hetzner
servers saved as ::64 that are not reachable, and adds brackets to
saved Sentinel URLs.
2026-10-09 13:50:28 +02:00

146 lines
4.7 KiB
PHP

<?php
use App\Support\SmtpTransportFactory;
use Symfony\Component\Mailer\Transport\Smtp\Stream\SocketStream;
function smtpSettings(array $overrides = []): object
{
return (object) array_merge([
'smtp_host' => 'smtp.example.com',
'smtp_port' => 25,
'smtp_encryption' => 'none',
'smtp_username' => 'user',
'smtp_password' => 'secret',
'smtp_timeout' => null,
], $overrides);
}
it('disables opportunistic STARTTLS when encryption is none', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'none',
]));
expect($transport->isAutoTls())->toBeFalse()
->and($transport->getStream())->toBeInstanceOf(SocketStream::class)
->and($transport->getStream()->isTLS())->toBeFalse();
});
it('does not issue STARTTLS for the issue 5877 anonymous port 25 relay', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'none',
'smtp_port' => 25,
'smtp_username' => '',
'smtp_password' => '',
]));
expect($transport->isAutoTls())->toBeFalse()
->and($transport->getStream()->isTLS())->toBeFalse()
->and($transport->getStream()->getPort())->toBe(25)
->and($transport->getUsername())->toBe('')
->and($transport->getPassword())->toBe('');
});
it('does not enable implicit TLS on port 465 when encryption is none', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'none',
'smtp_port' => 465,
]));
expect($transport->isAutoTls())->toBeFalse()
->and($transport->getStream()->isTLS())->toBeFalse();
});
it('keeps opportunistic STARTTLS when encryption is starttls', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'starttls',
]));
expect($transport->isAutoTls())->toBeTrue()
->and($transport->getStream()->isTLS())->toBeFalse();
});
it('uses implicit TLS when encryption is tls', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => 'tls',
'smtp_port' => 465,
]));
expect($transport->isAutoTls())->toBeTrue()
->and($transport->getStream()->isTLS())->toBeTrue();
});
it('infers implicit TLS on port 465 when encryption is null', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_encryption' => null,
'smtp_port' => 465,
]));
expect($transport->getStream()->isTLS())->toBeTrue();
});
it('applies a configured SMTP timeout to the transport stream', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings([
'smtp_timeout' => 15,
]));
expect($transport->getStream()->getTimeout())->toBe(15.0);
});
it('applies the configured ehlo domain to the transport', function () {
$transport = SmtpTransportFactory::fromSettings(
smtpSettings(['smtp_ehlo_domain' => 'team.example.com']),
'instance.example.com'
);
expect($transport->getLocalDomain())->toBe('team.example.com');
});
it('falls back to the instance ehlo domain for legacy smtp settings', function () {
$transport = SmtpTransportFactory::fromSettings(
smtpSettings(),
'instance.example.com'
);
expect($transport->getLocalDomain())->toBe('instance.example.com');
});
it('maps none encryption to laravel mailer options that disable auto tls', function () {
expect(SmtpTransportFactory::mailerOptions(smtpSettings([
'smtp_encryption' => 'none',
])))->toBe([
'scheme' => 'smtp',
'encryption' => null,
'auto_tls' => '0',
]);
});
it('maps encryption to laravel mailer options', function (?string $mode, ?string $scheme, ?string $encryption) {
expect(SmtpTransportFactory::mailerOptions(smtpSettings([
'smtp_encryption' => $mode,
])))->toBe([
'scheme' => $scheme,
'encryption' => $encryption,
'auto_tls' => $mode === 'none' ? '0' : '',
]);
})->with([
'none' => ['none', 'smtp', null],
'starttls' => ['starttls', 'smtp', null],
'tls' => ['tls', 'smtps', 'tls'],
'unset' => [null, null, null],
]);
it('brackets an IPv6 smtp host', function (string $host) {
$transport = SmtpTransportFactory::fromSettings(smtpSettings(['smtp_host' => $host]));
expect($transport->getStream()->getHost())->toBe('[2001:db8::25]');
})->with([
'raw' => '2001:db8::25',
'bracketed' => '[2001:db8::25]',
]);
it('keeps a hostname smtp host unchanged', function () {
$transport = SmtpTransportFactory::fromSettings(smtpSettings(['smtp_host' => 'smtp.example.com']));
expect($transport->getStream()->getHost())->toBe('smtp.example.com');
});