Skip to content

Commit ecef43d

Browse files
committed
refactor: add Address and use underlying email for assertions
1 parent 5d22961 commit ecef43d

File tree

10 files changed

+120
-81
lines changed

10 files changed

+120
-81
lines changed

packages/mailer/src/Address.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace Tempest\Mail;
4+
5+
use Stringable;
6+
7+
final class Address implements Stringable
8+
{
9+
public function __construct(
10+
public readonly string $email,
11+
public readonly ?string $name = null,
12+
) {}
13+
14+
public function __toString(): string
15+
{
16+
if ($this->name) {
17+
return sprintf('%s <%s>', $this->name, $this->email);
18+
}
19+
20+
return $this->email;
21+
}
22+
}

packages/mailer/src/EmailToSymfonyEmailMapper.php

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace Tempest\Mail;
66

7+
use Symfony\Component\Mime\Address as SymfonyAddress;
78
use Symfony\Component\Mime\Email as SymfonyEmail;
89
use Tempest\Mail\Exceptions\MissingExpeditorAddressException;
910
use Tempest\Mail\Exceptions\MissingRecipientAddressException;
@@ -12,6 +13,8 @@
1213
use Tempest\View\View;
1314
use Tempest\View\ViewRenderer;
1415

16+
use function Tempest\Support\arr;
17+
1518
final readonly class EmailToSymfonyEmailMapper implements Mapper
1619
{
1720
public function __construct(
@@ -35,38 +38,36 @@ public function map(mixed $from, mixed $to): SymfonyEmail
3538
$symfonyEmail = new SymfonyEmail();
3639

3740
if ($email->envelope->from) {
38-
$symfonyEmail->from(...Arr\wrap($email->envelope->from));
41+
$symfonyEmail->from(...$this->convertAddresses($email->envelope->from));
3942
} elseif ($this->mailerConfig->from) {
4043
$symfonyEmail->from($this->mailerConfig->from);
4144
} else {
4245
throw new MissingExpeditorAddressException();
4346
}
4447

4548
if ($email->envelope->to) {
46-
$symfonyEmail->to(...Arr\wrap($email->envelope->to));
49+
$symfonyEmail->to(...$this->convertAddresses($email->envelope->to));
4750
} else {
4851
throw new MissingRecipientAddressException();
4952
}
5053

5154
if ($email->envelope->cc) {
52-
$symfonyEmail->cc(...Arr\wrap($email->envelope->cc));
55+
$symfonyEmail->cc(...$this->convertAddresses($email->envelope->cc));
5356
}
5457

5558
if ($email->envelope->bcc) {
56-
$symfonyEmail->bcc(...Arr\wrap($email->envelope->bcc));
59+
$symfonyEmail->bcc(...$this->convertAddresses($email->envelope->bcc));
5760
}
5861

5962
if ($email->envelope->replyTo) {
60-
$symfonyEmail->replyTo(...Arr\wrap($email->envelope->replyTo));
63+
$symfonyEmail->replyTo(...$this->convertAddresses($email->envelope->replyTo));
6164
}
6265

6366
if ($email->envelope->subject) {
6467
$symfonyEmail->subject($email->envelope->subject);
6568
}
6669

67-
if ($email->envelope->priority) {
68-
$symfonyEmail->priority($email->envelope->priority->value);
69-
}
70+
$symfonyEmail->priority($email->envelope->priority->value);
7071

7172
if ($email->content->text) {
7273
$symfonyEmail->text($email->content->text);
@@ -85,4 +86,19 @@ public function map(mixed $from, mixed $to): SymfonyEmail
8586

8687
return $symfonyEmail;
8788
}
89+
90+
private function convertAddresses(null|string|array|Address $addresses): array
91+
{
92+
return arr($addresses)
93+
->map(function (string|Address|SymfonyAddress $address) {
94+
return match (true) {
95+
$address instanceof SymfonyAddress => $address,
96+
$address instanceof Address => new SymfonyAddress($address->email, $address->name),
97+
is_string($address) => SymfonyAddress::create($address),
98+
default => null,
99+
};
100+
})
101+
->filter()
102+
->toArray();
103+
}
88104
}

packages/mailer/src/Envelope.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
namespace Tempest\Mail;
44

5-
use Symfony\Component\Mime\Address;
6-
75
/**
86
* Represents the envelope of an email.
97
*/
@@ -16,6 +14,6 @@ public function __construct(
1614
public null|string|array|Address $cc = null,
1715
public null|string|array|Address $bcc = null,
1816
public null|string|array|Address $replyTo = null,
19-
public ?Priority $priority = null,
17+
public Priority $priority = Priority::NORMAL,
2018
) {}
2119
}

packages/mailer/src/MailerConfig.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
namespace Tempest\Mail;
44

55
use Symfony\Component\Mailer\Transport\TransportInterface;
6-
use Symfony\Component\Mime\Address;
76
use Tempest\Container\HasTag;
87

98
interface MailerConfig extends HasTag

packages/mailer/src/Testing/SentTestingEmail.php

Lines changed: 43 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,16 @@
44

55
use Closure;
66
use PHPUnit\Framework\Assert;
7-
use PHPUnit\Framework\AssertionFailedError;
8-
use PHPUnit\Framework\ExpectationFailedException;
9-
use PHPUnit\Framework\GeneratorNotSupportedException;
10-
use Symfony\Component\Mime\Address;
7+
use Symfony\Component\Mime\Address as SymfonyAddress;
118
use Symfony\Component\Mime\Email as SymfonyEmail;
9+
use Tempest\Mail\Address;
1210
use Tempest\Mail\Email;
1311
use Tempest\Mail\Priority;
1412
use Tempest\Mail\SentEmail;
1513
use Tempest\Support\Arr;
1614

15+
use function Tempest\Support\arr;
16+
1717
final class SentTestingEmail implements SentEmail
1818
{
1919
/**
@@ -28,15 +28,16 @@ final class SentTestingEmail implements SentEmail
2828
}
2929

3030
public string $raw {
31-
get => $this->html ?: $this->text;
31+
get => $this->symfonyEmail->getBody()->bodyToString();
32+
}
33+
34+
public string $id {
35+
get => $this->symfonyEmail->generateMessageId();
3236
}
3337

3438
public function __construct(
35-
private readonly Email $original,
36-
private readonly SymfonyEmail $symfonyEmail,
37-
public readonly string $id,
38-
public readonly ?string $html,
39-
public readonly ?string $text,
39+
private(set) readonly Email $original,
40+
private(set) readonly SymfonyEmail $symfonyEmail,
4041
) {}
4142

4243
/**
@@ -46,7 +47,7 @@ public function assertSubjectContains(string $expect): self
4647
{
4748
Assert::assertStringContainsString(
4849
needle: $expect,
49-
haystack: $this->original->envelope->subject,
50+
haystack: $this->symfonyEmail->getSubject(),
5051
message: "Failed asserting that the email's subject is `{$expect}`.",
5152
);
5253

@@ -59,7 +60,7 @@ public function assertSubjectContains(string $expect): self
5960
public function assertSentTo(string|array $addresses): self
6061
{
6162
return $this->assertAddressListContains(
62-
haystack: $this->original->envelope->to,
63+
haystack: $this->symfonyEmail->getTo(),
6364
needles: $addresses,
6465
message: 'Failed asserting that the email was sent to [%s]. The recipients are [%s].',
6566
);
@@ -71,7 +72,7 @@ public function assertSentTo(string|array $addresses): self
7172
public function assertNotSentTo(string|array $addresses): self
7273
{
7374
return $this->assertAddressListDoesNotContain(
74-
haystack: $this->original->envelope->to,
75+
haystack: $this->symfonyEmail->getTo(),
7576
needles: $addresses,
7677
message: 'Failed asserting that the email was not sent to [%s]. The recipients are [%s].',
7778
);
@@ -83,7 +84,7 @@ public function assertNotSentTo(string|array $addresses): self
8384
public function assertFrom(string|array $addresses): self
8485
{
8586
return $this->assertAddressListContains(
86-
haystack: $this->original->envelope->from,
87+
haystack: $this->symfonyEmail->getFrom(),
8788
needles: $addresses,
8889
message: 'Failed asserting that the email was sent from [%s]. The expeditors are [%s].',
8990
);
@@ -95,7 +96,7 @@ public function assertFrom(string|array $addresses): self
9596
public function assertNotFrom(string|array $addresses): self
9697
{
9798
return $this->assertAddressListDoesNotContain(
98-
haystack: $this->original->envelope->from,
99+
haystack: $this->symfonyEmail->getFrom(),
99100
needles: $addresses,
100101
message: 'Failed asserting that the email was not sent from [%s]. The expeditors are [%s].',
101102
);
@@ -107,7 +108,7 @@ public function assertNotFrom(string|array $addresses): self
107108
public function assertCarbonCopy(string|array $addresses): self
108109
{
109110
return $this->assertAddressListContains(
110-
haystack: $this->original->envelope->cc,
111+
haystack: $this->symfonyEmail->getCc(),
111112
needles: $addresses,
112113
message: 'Failed asserting that [%s] were included in carbon copies. The carbon copy recipients are [%s].',
113114
);
@@ -119,7 +120,7 @@ public function assertCarbonCopy(string|array $addresses): self
119120
public function assertNotCarbonCopy(string|array $addresses): self
120121
{
121122
return $this->assertAddressListDoesNotContain(
122-
haystack: $this->original->envelope->cc,
123+
haystack: $this->symfonyEmail->getCc(),
123124
needles: $addresses,
124125
message: 'Failed asserting that [%s] were not included in carbon copies. The carbonm copy recipients are [%s].',
125126
);
@@ -131,7 +132,7 @@ public function assertNotCarbonCopy(string|array $addresses): self
131132
public function assertBlindCarbonCopy(string|array $addresses): self
132133
{
133134
return $this->assertAddressListContains(
134-
haystack: $this->original->envelope->bcc,
135+
haystack: $this->symfonyEmail->getBcc(),
135136
needles: $addresses,
136137
message: 'Failed asserting that [%s] were included in blind carbon copies. The blind carbon copy recipients are [%s].',
137138
);
@@ -143,7 +144,7 @@ public function assertBlindCarbonCopy(string|array $addresses): self
143144
public function assertNotBlindCarbonCopy(string|array $addresses): self
144145
{
145146
return $this->assertAddressListDoesNotContain(
146-
haystack: $this->original->envelope->cc,
147+
haystack: $this->symfonyEmail->getBcc(),
147148
needles: $addresses,
148149
message: 'Failed asserting that [%s] were not included in blind carbon copies. The blind carbon copy recipients are [%s].',
149150
);
@@ -160,23 +161,8 @@ public function assertPriority(null|int|Priority $priority): self
160161

161162
Assert::assertSame(
162163
expected: $priority,
163-
actual: $this->original->envelope->priority,
164-
message: $this->original->envelope->priority
165-
? 'Failed asserting that the email has a priority of [%s]. The priority is [%s].'
166-
: 'Failed asserting that the email has a priority of [%s]. The email does not have a specific priority.',
167-
);
168-
169-
return $this;
170-
}
171-
172-
/**
173-
* Asserts that the email does not have a priority.
174-
*/
175-
public function assertNoPriority(): self
176-
{
177-
Assert::assertNull(
178-
actual: $this->original->envelope->priority,
179-
message: 'Failed asserting that the email does not have a priority.',
164+
actual: $this->symfonyEmail->getPriority(),
165+
message: 'Failed asserting that the email has a priority of [%s]. The priority is [%s].',
180166
);
181167

182168
return $this;
@@ -217,7 +203,7 @@ public function assertSeeInHtml(string $expect): self
217203
{
218204
Assert::assertStringContainsString(
219205
needle: $expect,
220-
haystack: $this->html,
206+
haystack: $this->symfonyEmail->getHtmlBody(),
221207
message: "Failed asserting that the email's HTML contains `{$expect}`.",
222208
);
223209

@@ -231,7 +217,7 @@ public function assertNotSeeInHtml(string $expect): self
231217
{
232218
Assert::assertStringNotContainsString(
233219
needle: $expect,
234-
haystack: $this->raw,
220+
haystack: $this->symfonyEmail->getHtmlBody(),
235221
message: "Failed asserting that the email's HTML does not contain `{$expect}`.",
236222
);
237223

@@ -245,7 +231,7 @@ public function assertSeeInText(string $expect): self
245231
{
246232
Assert::assertStringContainsString(
247233
needle: $expect,
248-
haystack: $this->text,
234+
haystack: $this->symfonyEmail->getTextBody(),
249235
message: "Failed asserting that the email's text contains `{$expect}`.",
250236
);
251237

@@ -259,7 +245,7 @@ public function assertNotSeeInText(string $expect): self
259245
{
260246
Assert::assertStringNotContainsString(
261247
needle: $expect,
262-
haystack: $this->text,
248+
haystack: $this->symfonyEmail->getTextBody(),
263249
message: "Failed asserting that the email's text does not contain `{$expect}`.",
264250
);
265251

@@ -296,13 +282,7 @@ public function assertAttached(string $filename, ?\Closure $callback = null): se
296282
private function assertAddressListContains(null|string|array|Address $haystack, string|array $needles, string $message): self
297283
{
298284
$needles = Arr\wrap($needles);
299-
$haystack = Arr\map_iterable(
300-
array: Arr\wrap($haystack),
301-
map: fn (Address|string $address) => match (true) {
302-
$address instanceof Address => $address->getAddress(),
303-
default => $address,
304-
},
305-
);
285+
$haystack = $this->convertAddresses($haystack);
306286

307287
foreach ($needles as $address) {
308288
Assert::assertContains(
@@ -318,13 +298,7 @@ private function assertAddressListContains(null|string|array|Address $haystack,
318298
private function assertAddressListDoesNotContain(null|string|array|Address $haystack, string|array $needles, string $message): self
319299
{
320300
$needles = Arr\wrap($needles);
321-
$haystack = Arr\map_iterable(
322-
array: Arr\wrap($haystack),
323-
map: fn (Address|string $address) => match (true) {
324-
$address instanceof Address => $address->getAddress(),
325-
default => $address,
326-
},
327-
);
301+
$haystack = $this->convertAddresses($haystack);
328302

329303
foreach ($needles as $address) {
330304
Assert::assertNotContains(
@@ -336,4 +310,19 @@ private function assertAddressListDoesNotContain(null|string|array|Address $hays
336310

337311
return $this;
338312
}
313+
314+
private function convertAddresses(null|string|array|Address $addresses): array
315+
{
316+
return arr($addresses)
317+
->map(function (string|Address|SymfonyAddress $address) {
318+
return match (true) {
319+
$address instanceof SymfonyAddress => $address->getAddress(),
320+
$address instanceof Address => $address->email,
321+
is_string($address) => $address,
322+
default => null,
323+
};
324+
})
325+
->filter()
326+
->toArray();
327+
}
339328
}

packages/mailer/src/Testing/TestingMailer.php

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,6 @@ public function send(Email $email): SentTestingEmail
4343
return new SentTestingEmail(
4444
original: $email,
4545
symfonyEmail: $symfonyEmail,
46-
id: Random\secure_string(length: 20),
47-
html: ($email->content->html instanceof View)
48-
? $this->viewRenderer->render($email->content->html)
49-
: $email->content->html,
50-
text: $email->content->text,
5146
);
5247
}
5348

packages/mailer/src/Transports/NullMailerConfig.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
use Symfony\Component\Mailer\Transport\NullTransport;
66
use Symfony\Component\Mailer\Transport\TransportInterface;
7-
use Symfony\Component\Mime\Address;
7+
use Tempest\Mail\Address;
88
use Tempest\Mail\MailerConfig;
99
use UnitEnum;
1010

packages/mailer/src/Transports/Postmark/PostmarkConfig.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
88
use Symfony\Component\Mailer\Transport\Dsn;
99
use Symfony\Component\Mailer\Transport\TransportInterface;
10-
use Symfony\Component\Mime\Address;
10+
use Tempest\Mail\Address;
1111
use Tempest\Mail\MailerConfig;
1212
use UnitEnum;
1313

0 commit comments

Comments
 (0)