From 00fa385ef1e5c93a2114ab60d8ccb07600a98401 Mon Sep 17 00:00:00 2001 From: Brandon Ferens Date: Fri, 18 Oct 2024 16:48:26 -0700 Subject: [PATCH] Attachment assertions --- README.md | 22 ++ src/AssertableMessage.php | 8 + src/Assertions/AttachmentAssertions.php | 147 +++++++++++++ src/WithMailInterceptor.php | 2 + tests/AttachmentAssertionsTest.php | 213 ++++++++++++++++++ tests/Fixtures/f-18.jpg | Bin 0 -> 30922 bytes tests/Fluent/AttachmentAssertionsTest.php | 250 ++++++++++++++++++++++ 7 files changed, 642 insertions(+) create mode 100644 src/Assertions/AttachmentAssertions.php create mode 100644 tests/AttachmentAssertionsTest.php create mode 100644 tests/Fixtures/f-18.jpg create mode 100644 tests/Fluent/AttachmentAssertionsTest.php diff --git a/README.md b/README.md index fa1dc02..23457c6 100644 --- a/README.md +++ b/README.md @@ -151,6 +151,17 @@ This should be called after `Mail` has been sent, but before your assertions, ot | `$intercepted->assertPriorityIsLowest();` | | | `$intercepted->assertPriorityIsLowest();` | | +| Attachment Assertions | Parameters | +|:-------------------------------------------------------|:-------------------| +| `$intercepted->assertHasAttachment($filename);` | `$filename` string | +| `$intercepted->assertHasAttachments();` | | +| `$intercepted->assertMissingAttachment($filename);` | `$filename` string | +| `$intercepted->assertMissingAttachments();` | | +| `$intercepted->assertHasEmbeddedImage($filename);` | `$filename` string | +| `$intercepted->assertHasEmbeddedImages();` | | +| `$intercepted->assertMissingEmbeddedImage($filename);` | `$filename` string | +| `$intercepted->assertMissingEmbeddedImages();` | | + #### Assertion Methods | Assertions | Parameters | @@ -211,6 +222,17 @@ This should be called after `Mail` has been sent, but before your assertions, ot | `$this->assertMailPriorityIsLowest($mail);` | `$mail` AssertableMessage, Email | | `$this->assertMailPriorityIsLowest($mail);` | `$mail` AssertableMessage, Email | +| Attachment Assertions | Parameters | +|:---------------------------------------------------|:--------------------------------------------------------| +| `this->assertMailHasAttachment($filename);` | `$filename` string
`$mail` AssertableMessage, Email | +| `this->assertMailHasAttachments();` | `$mail` AssertableMessage, Email | +| `this->assertMailMissingAttachment($filename);` | `$filename` string
`$mail` AssertableMessage, Email | +| `this->assertMailMissingAttachments();` | `$mail` AssertableMessage, Email | +| `this->assertMailHasEmbeddedImage($filename);` | `$filename` string
`$mail` AssertableMessage, Email | +| `this->assertMailHasEmbeddedImages();` | `$mail` AssertableMessage, Email | +| `this->assertMailMissingEmbeddedImage($filename);` | `$filename` string
`$mail` AssertableMessage, Email | +| `this->assertMailMissingEmbeddedImages();` | `$mail` AssertableMessage, Email | + You should use each item of the `interceptedMail()` collection as the mail object for all assertions. If you are injecting your own headers or need access to other headers in the email, use this assertion to verify they exist and are set properly. These assertions require the header name and the compiled email. diff --git a/src/AssertableMessage.php b/src/AssertableMessage.php index 7388c1e..cc51adb 100644 --- a/src/AssertableMessage.php +++ b/src/AssertableMessage.php @@ -54,6 +54,14 @@ * @method assertMissingHeader(string $expected) * @method assertHeaderIs(string $expected, string $expectedValue) * @method assertHeaderIsNot(string $expected, string $expectedValue) + * @method assertHasAttachment(string $filename) + * @method assertHasAttachments() + * @method assertMissingAttachment(string $filename) + * @method assertMissingAttachments() + * @method assertHasEmbeddedImage(string $filename) + * @method assertHasEmbeddedImages() + * @method assertMissingEmbeddedImage(string $filename) + * @method assertMissingEmbeddedImages() */ class AssertableMessage extends Assert { diff --git a/src/Assertions/AttachmentAssertions.php b/src/Assertions/AttachmentAssertions.php new file mode 100644 index 0000000..8be8471 --- /dev/null +++ b/src/Assertions/AttachmentAssertions.php @@ -0,0 +1,147 @@ +assertNotEmpty( + $mail->getAttachments(), + 'Mail missing expected attachments.' + ); + } + + /** + * Assert mail missing attachments. + */ + public function assertMailMissingAttachments(AssertableMessage|Email $mail): void + { + $this->assertEmpty( + $mail->getAttachments(), + 'Mail has expected attachments.' + ); + } + + /** + * Assert mail has attachment. + */ + public function assertMailHasAttachment(string $expected, AssertableMessage|Email $mail): void + { + $hasAttachment = collect($mail->getAttachments()) + ->contains(fn (DataPart $attachment) => $expected === $attachment->getFilename()); + + $this->assertTrue( + $hasAttachment, + 'Mail missing expected attachment.' + ); + } + + /** + * Assert mail missing attachment. + */ + public function assertMailMissingAttachment(string $expected, AssertableMessage|Email $mail): void + { + $missingAttachment = ! collect($mail->getAttachments()) + ->contains(fn (DataPart $attachment) => $expected === $attachment->getFilename()); + + $this->assertTrue( + $missingAttachment, + 'Mail has expected attachment.' + ); + } + + /* + |-------------------------------------------------------------------------- + | Embedded Images + |-------------------------------------------------------------------------- + */ + + /** + * Assert mail has attachments. + */ + public function assertMailHasEmbeddedImages(AssertableMessage|Email $mail): void + { + $hasEmbedded = collect($mail->getAttachments()) + ->contains(fn (DataPart $attachment) => $attachment->getDisposition() === 'inline'); + + $this->assertTrue( + $hasEmbedded, + 'Mail missing embedded images.' + ); + } + + /** + * Assert mail missing attachments. + */ + public function assertMailMissingEmbeddedImages(AssertableMessage|Email $mail): void + { + $missingEmbedded = ! collect($mail->getAttachments()) + ->contains(fn (DataPart $attachment) => $attachment->getDisposition() === 'inline'); + + $this->assertTrue( + $missingEmbedded, + 'Mail has embedded images.' + ); + } + + /** + * Assert mail has attachment. + */ + public function assertMailHasEmbeddedImage(string $expected, AssertableMessage|Email $mail): void + { + /** + * @var DataPart|null $embed + */ + $embed = collect($mail->getAttachments()) + ->firstWhere(fn (DataPart $attachment) => $expected === $attachment->getFilename()); + + if ($embed) { + $this->assertTrue( + $embed->getDisposition() === 'inline', + 'Mail has expected attachment but is not embedded.' + ); + + return; + } + + throw new ExpectationFailedException('Mail missing expected embedded image.'); + } + + /** + * Assert mail missing attachment. + */ + public function assertMailMissingEmbeddedImage(string $expected, AssertableMessage|Email $mail): void + { + /** + * @var DataPart|null $embed + */ + $embed = collect($mail->getAttachments()) + ->firstWhere(fn (DataPart $attachment) => $expected === $attachment->getFilename()); + + if (! $embed) { + $this->assertNull($embed); + + return; + } + + $this->assertTrue( + $embed->getDisposition() !== 'inline', + 'Mail has expected embedded image.' + ); + } +} diff --git a/src/WithMailInterceptor.php b/src/WithMailInterceptor.php index c22cd61..057b4db 100644 --- a/src/WithMailInterceptor.php +++ b/src/WithMailInterceptor.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Illuminate\Support\Facades\Config; +use KirschbaumDevelopment\MailIntercept\Assertions\AttachmentAssertions; use KirschbaumDevelopment\MailIntercept\Assertions\BccAssertions; use KirschbaumDevelopment\MailIntercept\Assertions\CcAssertions; use KirschbaumDevelopment\MailIntercept\Assertions\ContentAssertions; @@ -21,6 +22,7 @@ trait WithMailInterceptor { + use AttachmentAssertions; use BccAssertions; use CcAssertions; use ContentAssertions; diff --git a/tests/AttachmentAssertionsTest.php b/tests/AttachmentAssertionsTest.php new file mode 100644 index 0000000..cd0770f --- /dev/null +++ b/tests/AttachmentAssertionsTest.php @@ -0,0 +1,213 @@ +addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))); + + $this->assertMailHasAttachments($mail); + } + + public function testMailHasAttachmentsThrowsProperExpectationFailedException() + { + $mail = (new Email()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected attachments.'); + + $this->assertMailHasAttachments($mail); + } + + /* + |-------------------------------------------------------------------------- + | assertMailMissingAttachments + |-------------------------------------------------------------------------- + */ + + public function testMailMissingAttachments() + { + $mail = (new Email()); + + $this->assertMailMissingAttachments($mail); + } + + public function testMailMissingAttachmentsThrowsProperExpectationFailedException() + { + $mail = (new Email())->addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has expected attachments.'); + + $this->assertMailMissingAttachments($mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailHasAttachment + |-------------------------------------------------------------------------- + */ + + public function testMailHasAttachment() + { + $mail = (new Email())->addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))); + + $this->assertMailHasAttachment('f-18.jpg', $mail); + } + + public function testMailHasAttachmentThrowsProperExpectationFailedException() + { + $mail = (new Email()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected attachment.'); + + $this->assertMailHasAttachment('f-18.jpg', $mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailMissingAttachment + |-------------------------------------------------------------------------- + */ + + public function testMailMissingAttachment() + { + $mail = (new Email()); + + $this->assertMailMissingAttachment('f-18.jpg', $mail); + } + + public function testMailMissingAttachmentThrowsProperExpectationFailedException() + { + $mail = (new Email())->addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has expected attachment.'); + + $this->assertMailMissingAttachment('f-18.jpg', $mail); + } + + /* + |-------------------------------------------------------------------------- + | assertMailHasEmbeddedImages + |-------------------------------------------------------------------------- + */ + + public function testMailHasEmbeddedImages() + { + $mail = (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()); + + $this->assertMailHasEmbeddedImages($mail); + } + + public function testMailHasEmbeddedImagesThrowsProperExpectationFailedException() + { + $mail = (new Email()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing embedded images.'); + + $this->assertMailHasEmbeddedImages($mail); + } + + /* + |-------------------------------------------------------------------------- + | assertMailMissingEmbeddedImages + |-------------------------------------------------------------------------- + */ + + public function testMailMissingEmbeddedImages() + { + $mail = (new Email()); + + $this->assertMailMissingEmbeddedImages($mail); + } + + public function testMailMissingEmbeddedImagesThrowsProperExpectationFailedException() + { + $mail = (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has embedded images.'); + + $this->assertMailMissingEmbeddedImages($mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailHasEmbeddedImage + |-------------------------------------------------------------------------- + */ + + public function testMailHasEmbeddedImage() + { + $mail = (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()); + + $this->assertMailHasEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailHasEmbeddedImageThrowsProperExpectationFailedException() + { + $mail = (new Email()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected embedded image.'); + + $this->assertMailHasEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailHasEmbeddedImageButCheckingForWrongImageThrowsProperExpectationFailedException() + { + $mail = (new Email()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected embedded image.'); + + $this->assertMailHasEmbeddedImage('f-22.jpg', $mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailMissingEmbeddedImage + |-------------------------------------------------------------------------- + */ + + public function testMailMissingEmbeddedImage() + { + $mail = (new Email()); + + $this->assertMailMissingEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailMissingEmbeddedImageThrowsProperExpectationFailedException() + { + $mail = (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has expected embedded image.'); + + $this->assertMailMissingEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailHasEmbeddedImageButCheckingForDifferentImage() + { + $mail = (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()); + + $this->assertMailMissingEmbeddedImage('f-22.jpg', $mail); + } +} diff --git a/tests/Fixtures/f-18.jpg b/tests/Fixtures/f-18.jpg new file mode 100644 index 0000000000000000000000000000000000000000..cfab7de81089a3d98d1214b93ee0e5bfb3248e91 GIT binary patch literal 30922 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 ZRsR4WE3fx|t#i$&&y^#1Mann1|JnJAe_Q|n literal 0 HcmV?d00001 diff --git a/tests/Fluent/AttachmentAssertionsTest.php b/tests/Fluent/AttachmentAssertionsTest.php new file mode 100644 index 0000000..69303d4 --- /dev/null +++ b/tests/Fluent/AttachmentAssertionsTest.php @@ -0,0 +1,250 @@ +addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))) + ); + + $mail->assertHasAttachments($mail); + } + + public function testMailHasAttachmentsThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + new Email() + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected attachments.'); + + $mail->assertHasAttachments($mail); + } + + /* + |-------------------------------------------------------------------------- + | assertMailMissingAttachments + |-------------------------------------------------------------------------- + */ + + public function testMailMissingAttachments() + { + $mail = new AssertableMessage( + new Email() + ); + + $mail->assertMissingAttachments($mail); + } + + public function testMailMissingAttachmentsThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + (new Email())->addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has expected attachments.'); + + $mail->assertMissingAttachments($mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailHasAttachment + |-------------------------------------------------------------------------- + */ + + public function testMailHasAttachment() + { + $mail = new AssertableMessage( + (new Email())->addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))) + ); + + $mail->assertHasAttachment('f-18.jpg', $mail); + } + + public function testMailHasAttachmentThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + new Email() + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected attachment.'); + + $mail->assertHasAttachment('f-18.jpg', $mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailMissingAttachment + |-------------------------------------------------------------------------- + */ + + public function testMailMissingAttachment() + { + $mail = new AssertableMessage( + new Email() + ); + + $mail->assertMissingAttachment('f-18.jpg', $mail); + } + + public function testMailMissingAttachmentThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + (new Email())->addPart(new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg'))) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has expected attachment.'); + + $mail->assertMissingAttachment('f-18.jpg', $mail); + } + + /* + |-------------------------------------------------------------------------- + | assertMailHasEmbeddedImages + |-------------------------------------------------------------------------- + */ + + public function testMailHasEmbeddedImages() + { + $mail = new AssertableMessage( + (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()) + ); + + $mail->assertHasEmbeddedImages($mail); + } + + public function testMailHasEmbeddedImagesThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + new Email() + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing embedded images.'); + + $mail->assertHasEmbeddedImages($mail); + } + + /* + |-------------------------------------------------------------------------- + | assertMailMissingEmbeddedImages + |-------------------------------------------------------------------------- + */ + + public function testMailMissingEmbeddedImages() + { + $mail = new AssertableMessage( + new Email() + ); + + $mail->assertMissingEmbeddedImages($mail); + } + + public function testMailMissingEmbeddedImagesThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has embedded images.'); + + $mail->assertMissingEmbeddedImages($mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailHasEmbeddedImage + |-------------------------------------------------------------------------- + */ + + public function testMailHasEmbeddedImage() + { + $mail = new AssertableMessage( + (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()) + ); + + $mail->assertHasEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailHasEmbeddedImageThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + new Email() + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected embedded image.'); + + $mail->assertHasEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailHasEmbeddedImageButCheckingForWrongImageThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + new Email() + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail missing expected embedded image.'); + + $mail->assertHasEmbeddedImage('f-22.jpg', $mail); + } + + /* + |-------------------------------------------------------------------------- + | testMailMissingEmbeddedImage + |-------------------------------------------------------------------------- + */ + + public function testMailMissingEmbeddedImage() + { + $mail = new AssertableMessage( + new Email() + ); + + $mail->assertMissingEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailMissingEmbeddedImageThrowsProperExpectationFailedException() + { + $mail = new AssertableMessage( + (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()) + ); + + $this->expectException(ExpectationFailedException::class); + $this->expectExceptionMessage('Mail has expected embedded image.'); + + $mail->assertMissingEmbeddedImage('f-18.jpg', $mail); + } + + public function testMailHasEmbeddedImageButCheckingForDifferentImage() + { + $mail = new AssertableMessage( + (new Email())->addPart((new DataPart(new File(__DIR__ . '/../Fixtures/f-18.jpg')))->asInline()) + ); + + $mail->assertMissingEmbeddedImage('f-22.jpg', $mail); + } +}