Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
184 / 184
100.00% covered (success)
100.00%
4 / 4
CRAP
n/a
0 / 0
mailpitMessages
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
mailpitMessage
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
mailpitHeaders
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
mailpitDeleteAll
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3// ╔════════════════════════════════════════════════════════════╗
4// ║ MIT Licence (#Expat) - https://opensource.org/licenses/MIT ║
5// ║ Copyright 2026 Frederic Poeydomenge <dyno@phexium.com>     ║
6// ╚════════════════════════════════════════════════════════════╝
7
8declare(strict_types=1);
9
10pest()->group('integration');
11
12use Phexium\Plugin\Mailer\Adapter\PhpMailerAdapter;
13use Phexium\Plugin\Mailer\Exception\MailSendFailedException;
14use Phexium\Plugin\Mailer\MessageBuilder;
15
16function mailpitMessages(): array
17{
18    $response = file_get_contents('http://mailpit:8025/api/v1/messages');
19
20    return json_decode($response, true)['messages'];
21}
22
23function mailpitMessage(string $id): array
24{
25    $response = file_get_contents('http://mailpit:8025/api/v1/message/'.$id);
26
27    return json_decode($response, true);
28}
29
30function mailpitHeaders(string $id): array
31{
32    $response = file_get_contents('http://mailpit:8025/api/v1/message/'.$id.'/headers');
33
34    return json_decode($response, true);
35}
36
37function mailpitDeleteAll(): void
38{
39    $context = stream_context_create(['http' => ['method' => 'DELETE']]);
40    file_get_contents('http://mailpit:8025/api/v1/messages', false, $context);
41}
42
43beforeEach(function (): void {
44    mailpitDeleteAll();
45    $this->mailer = new PhpMailerAdapter('mailpit', 1025);
46});
47
48afterEach(function (): void {
49    mailpitDeleteAll();
50});
51
52describe('Sending', function (): void {
53    it('sends email via SMTP', function (): void {
54        $message = MessageBuilder::create()
55            ->from('sender@example.com', 'Sender')
56            ->to('to@example.com', 'Recipient')
57            ->subject('Integration test')
58            ->htmlBody('<h1>Hello</h1>')
59            ->build()
60        ;
61
62        $this->mailer->send($message);
63
64        $messages = mailpitMessages();
65        expect($messages)->toHaveCount(1);
66
67        $message = mailpitMessage('latest');
68        expect($message['Username'])->toBe(''); // Check SMTPAuth=false
69        expect($message['To'][0]['Address'])->toBe('to@example.com');
70        expect($message['To'][0]['Name'])->toBe('Recipient');
71        expect($message['Subject'])->toBe('Integration test');
72        expect($message['HTML'])->toContain('<h1>Hello</h1>');
73        expect($message['Text'])->toContain('Hello');
74    });
75
76    it('sends email with SMTP authentication', function (): void {
77        $mailer = new PhpMailerAdapter('mailpit', 1025, username: 'user', password: 'pass');
78
79        $message = MessageBuilder::create()
80            ->from('sender@example.com', 'Sender')
81            ->to('to@example.com', 'Recipient')
82            ->subject('Auth test')
83            ->htmlBody('<h1>Hello</h1>')
84            ->build()
85        ;
86
87        $mailer->send($message);
88
89        $messages = mailpitMessages();
90        expect($messages)->toHaveCount(1);
91
92        $message = mailpitMessage('latest');
93        expect($message['Username'])->toBe('user'); // Check SMTPAuth=true
94        expect($message['To'][0]['Address'])->toBe('to@example.com');
95        expect($message['To'][0]['Name'])->toBe('Recipient');
96        expect($message['Subject'])->toBe('Auth test');
97        expect($message['HTML'])->toContain('<h1>Hello</h1>');
98        expect($message['Text'])->toContain('Hello');
99    });
100
101    it('sends email with default recipients names', function (): void {
102        $message = MessageBuilder::create()
103            ->from('sender@example.com')
104            ->to('to@example.com')
105            ->cc('cc@example.com')
106            ->bcc('bcc@example.com')
107            ->replyTo('reply@example.com')
108            ->subject('Default recipients names')
109            ->textBody('Hello')
110            ->build()
111        ;
112
113        $this->mailer->send($message);
114
115        $messages = mailpitMessages();
116        expect($messages)->toHaveCount(1);
117
118        $message = mailpitMessage('latest');
119        expect($message['To'][0]['Address'])->toBe('to@example.com');
120        expect($message['To'][0]['Name'])->toBe('');
121        expect($message['Cc'][0]['Address'])->toBe('cc@example.com');
122        expect($message['Cc'][0]['Name'])->toBe('');
123        expect($message['Bcc'][0]['Address'])->toBe('bcc@example.com');
124        expect($message['Bcc'][0]['Name'])->toBe('');
125        expect($message['ReplyTo'][0]['Address'])->toBe('reply@example.com');
126        expect($message['ReplyTo'][0]['Name'])->toBe('');
127    });
128
129    it('sends email with recipients names', function (): void {
130        $message = MessageBuilder::create()
131            ->from('sender@example.com', 'Sender')
132            ->to('to@example.com', 'Recipient')
133            ->cc('cc@example.com', 'CarbonCopy')
134            ->bcc('bcc@example.com')
135            ->replyTo('reply@example.com', 'ReplyTo')
136            ->subject('Set recipients names')
137            ->textBody('Hello')
138            ->build()
139        ;
140
141        $this->mailer->send($message);
142
143        $messages = mailpitMessages();
144        expect($messages)->toHaveCount(1);
145
146        $message = mailpitMessage('latest');
147        expect($message['To'][0]['Address'])->toBe('to@example.com');
148        expect($message['To'][0]['Name'])->toBe('Recipient');
149        expect($message['Cc'][0]['Address'])->toBe('cc@example.com');
150        expect($message['Cc'][0]['Name'])->toBe('CarbonCopy');
151        expect($message['Bcc'][0]['Address'])->toBe('bcc@example.com');
152        expect($message['Bcc'][0]['Name'])->toBe('');
153        expect($message['ReplyTo'][0]['Address'])->toBe('reply@example.com');
154        expect($message['ReplyTo'][0]['Name'])->toBe('ReplyTo');
155    });
156
157    it('sends email with custom headers', function (): void {
158        $message = MessageBuilder::create()
159            ->from('sender@example.com')
160            ->to('to@example.com')
161            ->subject('Headers test')
162            ->textBody('Body')
163            ->header('X-Custom-Tag', 'billing')
164            ->build()
165        ;
166
167        $this->mailer->send($message);
168
169        $messages = mailpitMessages();
170        expect($messages)->toHaveCount(1);
171
172        $headers = mailpitHeaders('latest');
173        expect($headers['X-Custom-Tag'])->toContain('billing');
174    });
175
176    it('sends email with file and content attachments', function (): void {
177        $message = MessageBuilder::create()
178            ->from('sender@example.com')
179            ->to('to@example.com')
180            ->subject('Attachments test')
181            ->textBody('Body')
182            ->attach(__FILE__, 'test.bin', 'text/plain')
183            ->attachContent('inline content', 'data.bin', 'text/plain')
184            ->build()
185        ;
186
187        $this->mailer->send($message);
188
189        $messages = mailpitMessages();
190        expect($messages)->toHaveCount(1);
191
192        $message = mailpitMessage('latest');
193        expect($message['Attachments'])->toHaveCount(2);
194        expect($message['Attachments'][0]['ContentType'])->toBe('text/plain');
195        expect($message['Attachments'][1]['ContentType'])->toBe('text/plain');
196    });
197
198    it('sends email with attachments without explicit mimetype', function (): void {
199        $message = MessageBuilder::create()
200            ->from('sender@example.com')
201            ->to('to@example.com')
202            ->subject('Attachments test')
203            ->textBody('Body')
204            ->attach(__FILE__, 'data.png')
205            ->attachContent('inline content', 'data.csv')
206            ->build()
207        ;
208
209        $this->mailer->send($message);
210
211        $messages = mailpitMessages();
212        expect($messages)->toHaveCount(1);
213
214        $message = mailpitMessage('latest');
215        expect($message['Attachments'])->toHaveCount(2);
216        expect($message['Attachments'][0]['ContentType'])->toBe('application/x-httpd-php');
217        expect($message['Attachments'][1]['ContentType'])->toBe('text/csv');
218    });
219});
220
221describe('Error handling', function (): void {
222    it('throws MailSendFailedException with encryption against plain SMTP', function (string $encryption): void {
223        $mailer = new PhpMailerAdapter('mailpit', 1025, encryption: $encryption);
224
225        $message = MessageBuilder::create()
226            ->from('sender@example.com')
227            ->to('to@example.com')
228            ->subject('Encryption test')
229            ->textBody('Body')
230            ->build()
231        ;
232
233        expect(fn () => $mailer->send($message))
234            ->toThrow(MailSendFailedException::class)
235        ;
236    })->with(['tls', 'ssl']);
237
238    it('throws MailSendFailedException on connection error', function (): void {
239        $mailer = new PhpMailerAdapter('invalid-host', 9999);
240
241        $message = MessageBuilder::create()
242            ->from('sender@example.com')
243            ->to('to@example.com')
244            ->subject('Fail test')
245            ->textBody('Body')
246            ->build()
247        ;
248
249        expect(fn () => $mailer->send($message))
250            ->toThrow(MailSendFailedException::class)
251        ;
252    });
253});