diff --git a/bruno/subscribeme/Brevo/Add existing contacts to a list.bru b/bruno/subscribeme/Brevo/Add existing contacts to a list.bru index a50f488..f1951d8 100644 --- a/bruno/subscribeme/Brevo/Add existing contacts to a list.bru +++ b/bruno/subscribeme/Brevo/Add existing contacts to a list.bru @@ -21,7 +21,7 @@ headers { body:json { { "emails": [ - "eliot@rezo-zero.com" + "john.doe@contact.com" ] } } diff --git a/bruno/subscribeme/Brevo/Get a Contact.bru b/bruno/subscribeme/Brevo/Get a Contact.bru new file mode 100644 index 0000000..dcaf268 --- /dev/null +++ b/bruno/subscribeme/Brevo/Get a Contact.bru @@ -0,0 +1,29 @@ +meta { + name: Get a Contact + type: http + seq: 6 +} + +get { + url: {{brevo_base_url}}/v3/contacts/:identifier?startDate=&endDate= + body: none + auth: none +} + +params:query { + ~startDate: + ~endDate: +} + +params:path { + identifier: +} + +headers { + api-key: {{brevo_api_key}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/subscribeme/Brevo/Get a list's details.bru b/bruno/subscribeme/Brevo/Get a list's details.bru new file mode 100644 index 0000000..90524bd --- /dev/null +++ b/bruno/subscribeme/Brevo/Get a list's details.bru @@ -0,0 +1,24 @@ +meta { + name: Get a list's details + type: http + seq: 7 +} + +get { + url: {{brevo_base_url}}/v3/contacts/lists/:listId + body: none + auth: none +} + +params:path { + listId: 4 +} + +headers { + api-key: {{brevo_api_key}} +} + +settings { + encodeUrl: true + timeout: 0 +} diff --git a/bruno/subscribeme/OxiMailing/Add new contacts into the specified list..bru b/bruno/subscribeme/OxiMailing/Add new contacts into the specified list..bru index 2bd6141..4d16240 100644 --- a/bruno/subscribeme/OxiMailing/Add new contacts into the specified list..bru +++ b/bruno/subscribeme/OxiMailing/Add new contacts into the specified list..bru @@ -27,7 +27,7 @@ auth:basic { body:json { { "contacts" : { - "email@rezo-zero.com": { + "email@test.com": { "firstName": "firstName", "lastName": "lastName" } diff --git a/src/SubscribeMe/Exception/ApiResponseException.php b/src/SubscribeMe/Exception/ApiResponseException.php index f08502d..bc39bbe 100644 --- a/src/SubscribeMe/Exception/ApiResponseException.php +++ b/src/SubscribeMe/Exception/ApiResponseException.php @@ -8,7 +8,7 @@ final class ApiResponseException extends \RuntimeException { - public function __construct(private array $responseBody, Throwable $previous = null) + public function __construct(private array $responseBody, ?Throwable $previous = null, private ?int $statusCode = null) { parent::__construct('Api response error', 0, $previous); } @@ -17,4 +17,9 @@ public function getResponseBody(): array { return $this->responseBody; } + + public function getStatusCode(): ?int + { + return $this->statusCode; + } } diff --git a/src/SubscribeMe/Subscriber/BrevoSubscriber.php b/src/SubscribeMe/Subscriber/BrevoSubscriber.php index cb2e290..1fd707d 100644 --- a/src/SubscribeMe/Subscriber/BrevoSubscriber.php +++ b/src/SubscribeMe/Subscriber/BrevoSubscriber.php @@ -4,13 +4,11 @@ namespace SubscribeMe\Subscriber; -use JsonException; use Psr\Http\Client\ClientExceptionInterface; use SubscribeMe\Exception\ApiResponseException; use SubscribeMe\Exception\CannotSendTransactionalEmailException; use SubscribeMe\Exception\CannotSubscribeException; use SubscribeMe\Exception\ApiCredentialsException; -use SubscribeMe\Exception\UnsupportedUnsubscribePlatformException; use SubscribeMe\GDPR\UserConsent; use SubscribeMe\ValueObject\EmailAddress; @@ -84,7 +82,7 @@ protected function getAttributes(array $options, array $userConsents = []): arra } /** - * @throws JsonException + * @throws \JsonException|ApiResponseException */ protected function doSubscribe(string $uri, array $body): bool|int { @@ -122,12 +120,20 @@ protected function doSubscribe(string $uri, array $body): bool|int $body['message'] == 'Contact already exist') { return true; } + + return false; } + + /* + * Any other status code (401, 403, 429, 5xx…) is an unexpected error: do not fail + * silently, surface the status code and response body so the caller can diagnose it. + */ + /** @var array $body */ + $body = json_decode($res->getBody()->getContents(), true) ?? []; + throw new ApiResponseException($body, null, $res->getStatusCode()); } catch (ClientExceptionInterface $exception) { throw new CannotSubscribeException($exception->getMessage(), $exception); } - - return false; } /** @@ -241,11 +247,19 @@ public function unsubscribe(string $email): bool if (isset($body['success'])) { return true; } + + return false; } + + /* + * Any other status code (401, 403, 429, 5xx…) is an unexpected error: do not fail + * silently, surface the status code and response body so the caller can diagnose it. + */ + /** @var array $body */ + $body = json_decode($res->getBody()->getContents(), true) ?? []; + throw new ApiResponseException($body, null, $res->getStatusCode()); } catch (ClientExceptionInterface $exception) { throw new CannotSubscribeException($exception->getMessage(), $exception); } - - return false; } } diff --git a/src/SubscribeMe/Subscriber/ResponseValidationTrait.php b/src/SubscribeMe/Subscriber/ResponseValidationTrait.php index 1498241..fd4f95a 100644 --- a/src/SubscribeMe/Subscriber/ResponseValidationTrait.php +++ b/src/SubscribeMe/Subscriber/ResponseValidationTrait.php @@ -16,6 +16,6 @@ protected function validateResponse(ResponseInterface $response): string } /** @var array $result */ $result = json_decode($response->getBody()->getContents(), true); - throw new ApiResponseException($result); + throw new ApiResponseException($result, null, $response->getStatusCode()); } } diff --git a/src/SubscribeMe/Subscriber/SubscriberInterface.php b/src/SubscribeMe/Subscriber/SubscriberInterface.php index cfcb5a2..214bfb3 100644 --- a/src/SubscribeMe/Subscriber/SubscriberInterface.php +++ b/src/SubscribeMe/Subscriber/SubscriberInterface.php @@ -5,6 +5,7 @@ namespace SubscribeMe\Subscriber; use JsonException; +use SubscribeMe\Exception\ApiResponseException; use SubscribeMe\Exception\UnsupportedTransactionalEmailPlatformException; use SubscribeMe\Exception\UnsupportedUnsubscribePlatformException; use SubscribeMe\GDPR\UserConsent; @@ -25,14 +26,14 @@ public function setContactListId(?string $contactListId): SubscriberInterface; * @param array $options * @param UserConsent[] $userConsents * @return bool|int Contact ID if succeeded or false - * @throws JsonException + * @throws JsonException|ApiResponseException */ public function subscribe(string $email, array $options, array $userConsents = []): bool|int; /** * @param string $email * @return bool true on succeeded or false - * @throws JsonException|UnsupportedUnsubscribePlatformException + * @throws JsonException|UnsupportedUnsubscribePlatformException|ApiResponseException */ public function unsubscribe(string $email): bool; diff --git a/tests/BrevoMailerTest.php b/tests/BrevoMailerTest.php index 3ece387..f41faa1 100644 --- a/tests/BrevoMailerTest.php +++ b/tests/BrevoMailerTest.php @@ -10,6 +10,7 @@ use Nyholm\Psr7\Response; use PHPUnit\Framework\TestCase; use SubscribeMe\Exception\ApiCredentialsException; +use SubscribeMe\Exception\ApiResponseException; use SubscribeMe\Exception\CannotSendTransactionalEmailException; use SubscribeMe\Subscriber\BrevoSubscriber; use SubscribeMe\ValueObject\EmailAddress; @@ -160,6 +161,56 @@ public function testSubscribeWithoutId(): void $this->assertEquals('api.brevo.com', $requests[0]->getUri()->getHost()); } + /** + * @throws JsonException + */ + public function testSubscribeThrowsOnUnauthorized(): void + { + $client = new Client(); + $factory = new Psr17Factory(); + + $client->setDefaultResponse( + new Response(401, [], json_encode(['message' => 'IP address not authorized'], JSON_THROW_ON_ERROR)) + ); + + $brevoSubscriber = new BrevoSubscriber($client, $factory, $factory); + $brevoSubscriber->setContactListId('3,5'); + $brevoSubscriber->setApiKey('928f601b-5476-4480-8eb0-c8d979f3b68f'); + + try { + $brevoSubscriber->subscribe('elly@example.com', []); + $this->fail('Expected ApiResponseException was not thrown.'); + } catch (ApiResponseException $exception) { + $this->assertSame(401, $exception->getStatusCode()); + $this->assertSame('IP address not authorized', $exception->getResponseBody()['message']); + } + } + + /** + * @throws JsonException + */ + public function testUnsubscribeThrowsOnUnauthorized(): void + { + $client = new Client(); + $factory = new Psr17Factory(); + + $client->setDefaultResponse( + new Response(401, [], json_encode(['message' => 'IP address not authorized'], JSON_THROW_ON_ERROR)) + ); + + $brevoSubscriber = new BrevoSubscriber($client, $factory, $factory); + $brevoSubscriber->setContactListId('3'); + $brevoSubscriber->setApiKey('928f601b-5476-4480-8eb0-c8d979f3b68f'); + + try { + $brevoSubscriber->unsubscribe('elly@example.com'); + $this->fail('Expected ApiResponseException was not thrown.'); + } catch (ApiResponseException $exception) { + $this->assertSame(401, $exception->getStatusCode()); + $this->assertSame('IP address not authorized', $exception->getResponseBody()['message']); + } + } + /** * @throws JsonException */