implicit)->toBe($implicit) ->and($policy->autoTls)->toBe($autoTls) ->and($policy->requireTls)->toBe($requireTls); })->with([ // encryption, port, implicit, autoTls, requireTls // 'ssl' is implicit TLS from the first byte, on any port — unchanged // from before this fix. 'ssl on the submission port' => ['ssl', 587, true, true, true], 'ssl on the SMTPS port' => ['ssl', 465, true, true, true], // Port 465 forces implicit TLS regardless of the stored encryption // setting, exactly as before this fix: a server listening there speaks // TLS from byte one and will not answer a plaintext EHLO, so a // mismatched setting must not turn into a hang. 'tls on the SMTPS port defers to the port' => ['tls', 465, true, true, true], 'none on the SMTPS port defers to the port' => ['none', 465, true, true, true], // 'tls' is the fix: STARTTLS is REQUIRED, not merely attempted. autoTls // must stay on too, or the STARTTLS upgrade requireTls checks for would // never be attempted in the first place. 'tls on the submission port' => ['tls', 587, false, true, true], 'tls on a custom port' => ['tls', 2525, false, true, true], // 'none' is the other half of the fix: no implicit wrap, and autoTls // OFF — otherwise a server that happens to offer STARTTLS would still // silently upgrade despite the operator's explicit choice. 'none on the submission port' => ['none', 587, false, false, false], 'none on a custom port' => ['none', 2525, false, false, false], // Blank or unrecognised values (a legacy row predating the in:tls,ssl,none // validation, or one that decoded to something unexpected) fall to the // same shape as 'none': no implicit wrap, no silent upgrade, no // spurious failure for a value nobody explicitly chose as 'tls'. 'blank falls back to the none shape' => ['', 587, false, false, false], 'garbage falls back to the none shape' => ['garbage', 587, false, false, false], ]); it('applies autoTls and requireTls to an already-built transport', function () { $transport = new EsmtpTransport('mail.example.test', 587, false); $policy = MailTlsPolicy::for('tls', 587); $returned = $policy->apply($transport); expect($returned)->toBe($transport) // fluent — the caller chains straight through ->and($transport->isAutoTls())->toBeTrue() ->and($transport->isTlsRequired())->toBeTrue(); });