diff --git a/src/ConfigProvider.php b/src/ConfigProvider.php index a0c2658..97748fc 100644 --- a/src/ConfigProvider.php +++ b/src/ConfigProvider.php @@ -43,7 +43,7 @@ class ConfigProvider { - public function __invoke() + public function __invoke(): array { $module = new Module(); $config = $module->getConfig(); diff --git a/src/Service/SparkPostService.php b/src/Service/SparkPostService.php index 61ba260..f7c9d3c 100644 --- a/src/Service/SparkPostService.php +++ b/src/Service/SparkPostService.php @@ -30,6 +30,13 @@ class SparkPostService extends AbstractMailService */ protected $apiKey; + public const SUPPRESSION_LIST_TRANSACTIONAL = 'transactional'; + public const SUPPRESSION_LIST_NON_TRANSACTIONAL = 'non_transactional'; + public const SUPPRESSION_LISTS = [ + self::SUPPRESSION_LIST_TRANSACTIONAL, + self::SUPPRESSION_LIST_NON_TRANSACTIONAL, + ]; + /** * Constructor */ @@ -311,6 +318,54 @@ public function registerSendingDomain(string $domain, array $options = []): bool return false; } + /** + * Add an email address to the suppression lists for transactional email, non-transactional email, or both + */ + public function addToSuppressionList(string $emailAddress, string $reason, array $suppressionLists = self::SUPPRESSION_LISTS): void + { + $put = [ + 'recipients' => [], + ]; + + foreach($suppressionLists as $suppressionList) { + if(in_array($suppressionList, self::SUPPRESSION_LISTS)) { + $put['recipients'][] = array( + 'recipient' => $emailAddress, + 'type' => $suppressionList, + 'description' => $reason, + ); + } + } + + if ($put['recipients']) { + $response = $this->prepareHttpClient('/suppression-list/', $put) + ->setMethod(HttpRequest::METHOD_PUT) + ->send(); + + $this->parseResponse($response); + } + } + + /** + * Remove an email address from the suppression lists for transactional email, non-transactional email, or both + */ + public function removeFromSuppressionList(string $emailAddress, array $suppressionLists = self::SUPPRESSION_LISTS): void + { + foreach($suppressionLists as $suppressionList) { + if (in_array($suppressionList, self::SUPPRESSION_LISTS)) { + $delete = [ + 'type' => $suppressionList, + ]; + + $response = $this->prepareHttpClient('/suppression-list/' . urlencode($emailAddress), $delete) + ->setMethod(HttpRequest::METHOD_DELETE) + ->send(); + + $this->parseResponse($response, [403, 404]); + } + } + } + public function verifySendingDomain(string $domain, array $options = []): bool { $dkimVerify = array_key_exists('dkim_verify', $options) && $options['dkim_verify'] === true; @@ -375,8 +430,8 @@ private function prepareHttpClient(string $uri, array $parameters = []): HttpCli } /** - * @param HttpResponse $response - * + * @param HttpResponse $response + * @param int[] $successCodes * @throws RuntimeException * @return array */ diff --git a/tests/SlmMailTest/Service/SparkPostServiceTest.php b/tests/SlmMailTest/Service/SparkPostServiceTest.php index df79a20..74b32cf 100644 --- a/tests/SlmMailTest/Service/SparkPostServiceTest.php +++ b/tests/SlmMailTest/Service/SparkPostServiceTest.php @@ -7,8 +7,6 @@ use Laminas\Mail\Address; use Laminas\Mail\AddressList; use Laminas\Mail\Message; -use PHPUnit\Framework\MockObject\MockBuilder; -use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use ReflectionMethod; use SlmMail\Mail\Message\SparkPost; @@ -43,7 +41,7 @@ private function expectApiResponse(int $statusCode = 200, string $responseBody = } $sendMessageResponse->setContent($responseBody); - $httpClientMock->expects($this->once()) + $httpClientMock->expects($this->atLeastOnce()) ->method('send') ->willReturn($sendMessageResponse); @@ -190,14 +188,16 @@ public function testRemoveSendingDomain() { /** @var SparkPostService $sparkPostServiceMock */ $sparkPostServiceMock = $this->expectApiResponse(204); - $this->assertNull($sparkPostServiceMock->removeSendingDomain('sparkpost-sending-domain.com')); + $sparkPostServiceMock->removeSendingDomain('sparkpost-sending-domain.com'); + $this->doesNotPerformAssertions(); } public function testRemoveNonExistingSendingDomain() { /** @var SparkPostService $sparkPostServiceMock */ $sparkPostServiceMock = $this->expectApiResponse(404); - $this->assertNull($sparkPostServiceMock->removeSendingDomain('sparkpost-sending-domain.com')); + $sparkPostServiceMock->removeSendingDomain('sparkpost-sending-domain.com'); + $this->doesNotPerformAssertions(); } public function testVerifySendingDomain() @@ -232,7 +232,7 @@ public function testVerifySendingDomainWithInvalidDkimRecord() public function testVerifyUnregisteredSendingDomain() { - //** @var SparkPostService $sparkPostServiceMock */ + /** @var SparkPostService $sparkPostServiceMock */ $sparkPostServiceMock = $this->expectApiResponse( 404, '{"errors":[{"message":"invalid params","description":"Sending domain \'sparkpost-sending-domain.com\' is not a registered sending domain","code":"1200"}]}' @@ -240,4 +240,44 @@ public function testVerifyUnregisteredSendingDomain() $this->expectException(RuntimeException::class); $sparkPostServiceMock->verifySendingDomain('sparkpost-sending-domain.com'); } + + public function testAddToSuppressionList() + { + /** @var SparkPostService $sparkPostServiceMock */ + $sparkPostServiceMock = $this->expectApiResponse(200, '{"results":{"message":"Suppression List successfully updated"}}'); + $sparkPostServiceMock->addToSuppressionList('sender@sending-domain.com', 'Permanent block after hard bounce'); + $this->doesNotPerformAssertions(); + } + + public function testAddToTransactionalSuppressionList() + { + /** @var SparkPostService $sparkPostServiceMock */ + $sparkPostServiceMock = $this->expectApiResponse(200, '{"results":{"message":"Suppression List successfully updated"}}'); + $sparkPostServiceMock->addToSuppressionList('sender@sending-domain.com', 'Permanent block after hard bounce', [SparkPostService::SUPPRESSION_LIST_TRANSACTIONAL]); + $this->doesNotPerformAssertions(); + } + + public function testRemoveFromSuppressionList() + { + /** @var SparkPostService $sparkPostServiceMock */ + $sparkPostServiceMock = $this->expectApiResponse(204); + $sparkPostServiceMock->removeFromSuppressionList('sender@sending-domain.com'); + $this->doesNotPerformAssertions(); + } + + public function testRemoveFromTransactionalSuppressionList() + { + /** @var SparkPostService $sparkPostServiceMock */ + $sparkPostServiceMock = $this->expectApiResponse(204); + $sparkPostServiceMock->removeFromSuppressionList('sender@sending-domain.com', [SparkPostService::SUPPRESSION_LIST_TRANSACTIONAL]); + $this->doesNotPerformAssertions(); + } + + public function testRemoveNonExistingAddressFromSuppressionList() + { + /** @var SparkPostService $sparkPostServiceMock */ + $sparkPostServiceMock = $this->expectApiResponse(404); + $sparkPostServiceMock->removeFromSuppressionList('sender@sending-domain.com'); + $this->doesNotPerformAssertions(); + } }