diff --git a/composer.json b/composer.json index b221f9d..b24cf97 100644 --- a/composer.json +++ b/composer.json @@ -19,9 +19,7 @@ "php": "^8.4", "ext-curl": "*", "ext-json": "*", - "guzzlehttp/guzzle": "7.x", "illuminate/contracts": "^10.0||^11.0||^12.0", - "league/oauth2-client": "^2", "spatie/laravel-data": "^4.15", "spatie/laravel-package-tools": "^1.16" }, diff --git a/src/Concerns/InteractsWithHttpRequests.php b/src/Concerns/InteractsWithHttpRequests.php new file mode 100644 index 0000000..8469568 --- /dev/null +++ b/src/Concerns/InteractsWithHttpRequests.php @@ -0,0 +1,82 @@ +<?php + +namespace Coderflex\LaravelSendy\Concerns; + +use Coderflex\LaravelSendy\Exceptions\InvalidApiKeyException; +use Coderflex\LaravelSendy\Exceptions\InvalidApiUrlException; +use Exception; +use Illuminate\Support\Facades\Http; + +/** + * @method static \Illuminate\Http\Client\Response get(string $path, array $data = [], bool $async = false, array $headers = []) + * @method static \Illuminate\Http\Client\Response post(string $path, array $data = [], bool $async = false, array $headers = []) + * @method static \Illuminate\Http\Client\Response put(string $path, array $data = [], bool $async = false, array $headers = []) + * @method static \Illuminate\Http\Client\Response delete(string $path, array $data = [], bool $async = false, array $headers = []) + * @method static \Illuminate\Http\Client\Response patch(string $path, array $data = [], bool $async = false, array $headers = []) + */ +trait InteractsWithHttpRequests +{ + public function __call(string $function, array $args): mixed + { + $options = ['get', 'post', 'put', 'delete', 'patch']; + $path = $args[0] ?? null; + $data = $args[1] ?? []; + $async = $args[2] ?? false; + $headers = $args[3] ?? []; + + if (! in_array($function, $options)) { + throw new Exception("Method {$function} not found."); + } + + return self::sendRequest( + type: $function, + request: $path, + data: $data, + headers: $headers, + async: $async + ); + } + + /** + * @throws \Exception + */ + protected function sendRequest(string $type, string $request, array $data = [], array $headers = [], bool $async = false): mixed + { + try { + $apiKey = config('laravel-sendy.api_key'); + $apiUrl = config('laravel-sendy.api_url'); + + throw_if( + blank($apiKey), + InvalidApiKeyException::class, + ); + + throw_if( + blank($apiUrl), + InvalidApiUrlException::class, + ); + + $payload = array_merge($data, [ + 'api_key' => $apiKey, + ]); + + $url = rtrim($apiUrl, '/').'/'.ltrim($request, '/'); + + $client = Http::withHeaders(array_merge([ + 'Content-Type' => 'application/json', + 'Accept' => 'application/json', + ], $headers ?? [])); + + return $async + ? $client->async()->{$type}($url, $payload) + : $client->{$type}($url, $payload); + + } catch (InvalidApiKeyException $th) { + throw new InvalidApiKeyException('Error: '.$th->getMessage()); + } catch (InvalidApiUrlException $th) { + throw new InvalidApiUrlException('Error: '.$th->getMessage()); + } catch (Exception $th) { + throw new Exception('Error: '.$th->getMessage()); + } + } +} diff --git a/src/DTOs/CompaignDTO.php b/src/DTOs/Campaigns/CampaignDTO.php similarity index 75% rename from src/DTOs/CompaignDTO.php rename to src/DTOs/Campaigns/CampaignDTO.php index 9ec47e1..a2cf8cd 100644 --- a/src/DTOs/CompaignDTO.php +++ b/src/DTOs/Campaigns/CampaignDTO.php @@ -1,11 +1,11 @@ <?php -namespace Coderflex\LaravelSendy\DTOs; +namespace Coderflex\LaravelSendy\DTOs\Campaigns; use Spatie\LaravelData\Data; use Spatie\LaravelData\Support\Validation\ValidationContext; -class CompaignDTO extends Data +class CampaignDTO extends Data { public function __construct( public string $from_name, @@ -15,15 +15,15 @@ public function __construct( public string $subject, public ?string $plain_text, public string $html_text, - public string $list_ids, - public string $segment_ids, + public ?string $list_ids, + public ?string $segment_ids, public ?string $exclude_list_ids, public ?string $exclude_segment_ids, - public string $brand_id, + public ?string $brand_id, public ?string $query_string, public ?int $track_opens, public ?int $track_clicks, - public ?int $send_compaign, + public ?int $send_campaign, public ?string $schedule_date_time, public ?string $schedule_timezone, ) {} @@ -38,15 +38,15 @@ public static function rules(ValidationContext $context): array 'subject' => ['required', 'string'], 'plain_text' => ['string', 'nullable'], 'html_text' => ['required', 'string'], - 'list_ids' => ['required', 'string'], - 'segment_ids' => ['required', 'string'], + 'list_ids' => ['required_if:send_campaign,1', 'string'], + 'segment_ids' => ['required_if:send_campaign,1', 'string'], 'exclude_list_ids' => ['string', 'nullable'], 'exclude_segment_ids' => ['string', 'nullable'], - 'brand_id' => ['required', 'string'], + 'brand_id' => ['required_if:send_campaign,0', 'string'], 'query_string' => ['string', 'nullable'], 'track_opens' => ['integer', 'nullable', 'in:0,1,2'], 'track_clicks' => ['integer', 'nullable', 'in:0,1,2'], - 'send_compaign' => ['integer', 'nullable', 'in:0,1'], + 'send_campaign' => ['integer', 'nullable', 'in:0,1'], 'schedule_date_time' => ['date', 'nullable'], 'schedule_timezone' => ['date', 'nullable'], ]; diff --git a/src/DTOs/Lists/ListsDTO.php b/src/DTOs/Lists/ListsDTO.php new file mode 100644 index 0000000..0c99256 --- /dev/null +++ b/src/DTOs/Lists/ListsDTO.php @@ -0,0 +1,21 @@ +<?php + +namespace Coderflex\LaravelSendy\DTOs\Lists; + +use Spatie\LaravelData\Data; +use Spatie\LaravelData\Support\Validation\ValidationContext; + +class ListsDTO extends Data +{ + public function __construct( + public string $brand_id, + public ?string $include_hidden = 'no', + ) {} + + public static function rules(ValidationContext $context): array + { + return [ + 'include_hidden' => ['in:yes,no'], + ]; + } +} diff --git a/src/DTOs/Subscribers/DeleteSubscriberDTO.php b/src/DTOs/Subscribers/DeleteSubscriberDTO.php new file mode 100644 index 0000000..a679be0 --- /dev/null +++ b/src/DTOs/Subscribers/DeleteSubscriberDTO.php @@ -0,0 +1,23 @@ +<?php + +namespace Coderflex\LaravelSendy\DTOs\Subscribers; + +use Spatie\LaravelData\Attributes\MergeValidationRules; +use Spatie\LaravelData\Data; +use Spatie\LaravelData\Support\Validation\ValidationContext; + +#[MergeValidationRules] +class DeleteSubscriberDTO extends Data +{ + public function __construct( + public string $list_id, + public string $email, + ) {} + + public static function rules(ValidationContext $context): array + { + return [ + 'email' => ['email'], + ]; + } +} diff --git a/src/DTOs/SubscribersDTO.php b/src/DTOs/Subscribers/SubscribeDTO.php similarity index 50% rename from src/DTOs/SubscribersDTO.php rename to src/DTOs/Subscribers/SubscribeDTO.php index 0ba3734..ed32fdf 100644 --- a/src/DTOs/SubscribersDTO.php +++ b/src/DTOs/Subscribers/SubscribeDTO.php @@ -1,11 +1,13 @@ <?php -namespace Coderflex\LaravelSendy\DTOs; +namespace Coderflex\LaravelSendy\DTOs\Subscribers; +use Spatie\LaravelData\Attributes\MergeValidationRules; use Spatie\LaravelData\Data; use Spatie\LaravelData\Support\Validation\ValidationContext; -class SubscribersDTO extends Data +#[MergeValidationRules] +class SubscribeDTO extends Data { public function __construct( public ?string $name, @@ -22,15 +24,8 @@ public function __construct( public static function rules(ValidationContext $context): array { return [ - 'name' => ['string', 'nullable'], - 'email' => ['required', 'string', 'email'], - 'list' => ['required', 'string'], - 'country' => ['string', 'nullable'], - 'ipaddress' => ['string', 'nullable', 'ip'], - 'referrer' => ['string', 'nullable'], - 'gdpr' => ['boolean', 'nullable'], - 'silent' => ['boolean', 'nullable'], - 'boolean' => ['boolean', 'nullable'], + 'email' => ['email'], + 'ipaddress' => ['ip'], ]; } } diff --git a/src/DTOs/Subscribers/SubscriberStatusDTO.php b/src/DTOs/Subscribers/SubscriberStatusDTO.php new file mode 100644 index 0000000..4c23a2b --- /dev/null +++ b/src/DTOs/Subscribers/SubscriberStatusDTO.php @@ -0,0 +1,23 @@ +<?php + +namespace Coderflex\LaravelSendy\DTOs\Subscribers; + +use Spatie\LaravelData\Attributes\MergeValidationRules; +use Spatie\LaravelData\Data; +use Spatie\LaravelData\Support\Validation\ValidationContext; + +#[MergeValidationRules] +class SubscriberStatusDTO extends Data +{ + public function __construct( + public string $list_id, + public string $email, + ) {} + + public static function rules(ValidationContext $context): array + { + return [ + 'email' => ['email'], + ]; + } +} diff --git a/src/DTOs/Subscribers/UnsubscribeDTO.php b/src/DTOs/Subscribers/UnsubscribeDTO.php new file mode 100644 index 0000000..cb69897 --- /dev/null +++ b/src/DTOs/Subscribers/UnsubscribeDTO.php @@ -0,0 +1,24 @@ +<?php + +namespace Coderflex\LaravelSendy\DTOs\Subscribers; + +use Spatie\LaravelData\Attributes\MergeValidationRules; +use Spatie\LaravelData\Data; +use Spatie\LaravelData\Support\Validation\ValidationContext; + +#[MergeValidationRules] +class UnsubscribeDTO extends Data +{ + public function __construct( + public string $list, + public string $email, + public ?bool $boolean = false, // plain text response + ) {} + + public static function rules(ValidationContext $context): array + { + return [ + 'email' => ['email'], + ]; + } +} diff --git a/src/Exceptions/CompaingException.php b/src/Exceptions/CompaingException.php deleted file mode 100644 index f2aa4df..0000000 --- a/src/Exceptions/CompaingException.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php - -namespace Coderflex\LaravelSendy\DTOs; - -class CompaingException extends \Exception -{ - // -} diff --git a/src/Exceptions/InvalidApiKeyException.php b/src/Exceptions/InvalidApiKeyException.php new file mode 100644 index 0000000..302f2e2 --- /dev/null +++ b/src/Exceptions/InvalidApiKeyException.php @@ -0,0 +1,10 @@ +<?php + +namespace Coderflex\LaravelSendy\Exceptions; + +class InvalidApiKeyException extends \Exception +{ + protected $message = 'The API key is invalid. Please check your configuration and try again.'; + + protected $code = 401; +} diff --git a/src/Exceptions/InvalidApiUrlException.php b/src/Exceptions/InvalidApiUrlException.php new file mode 100644 index 0000000..cc31a54 --- /dev/null +++ b/src/Exceptions/InvalidApiUrlException.php @@ -0,0 +1,10 @@ +<?php + +namespace Coderflex\LaravelSendy\Exceptions; + +class InvalidApiUrlException extends \Exception +{ + protected $message = 'The API URL is invalid. Please check your configuration and try again.'; + + protected $code = 401; +} diff --git a/src/Exceptions/SubscribersException.php b/src/Exceptions/SubscribersException.php deleted file mode 100644 index fbb1591..0000000 --- a/src/Exceptions/SubscribersException.php +++ /dev/null @@ -1,8 +0,0 @@ -<?php - -namespace Coderflex\LaravelSendy\DTOs; - -class SubscribersException extends \Exception -{ - // -} diff --git a/src/Facades/LaravelSendy.php b/src/Facades/LaravelSendy.php index 702b104..f98ecbc 100644 --- a/src/Facades/LaravelSendy.php +++ b/src/Facades/LaravelSendy.php @@ -6,6 +6,11 @@ /** * @see \Coderflex\LaravelSendy\LaravelSendy + * + * @method static \Coderflex\LaravelSendy\Resources\Subscribers subscribers() + * @method static \Coderflex\LaravelSendy\Resources\Lists lists() + * @method static \Coderflex\LaravelSendy\Resources\Brands brands() + * @method static \Coderflex\LaravelSendy\Resources\Campaigns campaigns() */ class LaravelSendy extends Facade { diff --git a/src/LaravelSendy.php b/src/LaravelSendy.php index 5dd50c2..59e7257 100644 --- a/src/LaravelSendy.php +++ b/src/LaravelSendy.php @@ -2,113 +2,29 @@ namespace Coderflex\LaravelSendy; -use Coderflex\LaravelSendy\Resources\Resources\Brands; -use Coderflex\LaravelSendy\Resources\Resources\Campaigns; -use Coderflex\LaravelSendy\Resources\Resources\Lists; -use Coderflex\LaravelSendy\Resources\Resources\Subscribers; -use Exception; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; +use Coderflex\LaravelSendy\Concerns\InteractsWithHttpRequests; class LaravelSendy { - protected string $apiKey; + use InteractsWithHttpRequests; - protected string $apiUrl; - - public function __construct() - { - if (blank(config('laravel-sendy.api_key'))) { - throw new Exception('API Key is not set in the config file.'); - } - - if (blank(config('laravel-sendy.api_url'))) { - throw new Exception('API URL is not set in the config file.'); - } - - $this->apiKey = config('laravel-sendy.api_key'); - $this->apiUrl = config('laravel-sendy.api_url'); - } - - public function subscribers(): Subscribers - { - return new Subscribers; - } - - public function lists(): Lists - { - return new Lists; - } - - public function brands(): Brands + public function subscribers(): Resources\Subscribers { - return new Brands; + return new Resources\Subscribers; } - public function campaigns(): Campaigns + public function lists(): Resources\Lists { - return new Campaigns; + return new Resources\Lists; } - public function __call(string $function, array $args) + public function brands(): Resources\Brands { - $options = ['get', 'post', 'put', 'delete', 'patch']; - $path = (isset($args[0])) ? $args[0] : null; - $data = (isset($args[1])) ? $args[1] : []; - $headers = (isset($args[2])) ? $args[2] : []; - - if (! in_array($function, $options)) { - throw new Exception("Method {$function} not found."); - } - - return self::guzzle( - type: $function, - request: $path, - data: $data, - headers: $headers - ); - } - - /** - * @throws \Exception - */ - protected function guzzle(string $type, string $request, array $data = [], array $headers = []): mixed - { - try { - $client = new Client; - - $mainHeaders = [ - 'Content-Type' => 'application/json', - 'Accept' => 'application/json', - ]; - - $headers = is_array($headers) && count($headers) > 0 - ? array_merge($mainHeaders, $headers) - : $mainHeaders; - - $response = $client->{$type}($this->apiUrl.$request, [ - 'headers' => $headers, - 'body' => json_encode(array_merge($data, [ - 'api_key' => $this->apiKey, - ])), - ]); - - $responseObject = $response->getBody()->getContents(); - - return $this->isJson($responseObject) - ? json_decode($responseObject, true) - : $responseObject; - - } catch (ClientException $th) { - throw new Exception('Error: '.$th->getMessage()); - } catch (Exception $th) { - throw new Exception('Error: '.$th->getMessage()); - } + return new Resources\Brands; } - protected function isJson(string $string): bool + public function campaigns(): Resources\Campaigns { - return is_array(json_decode($string)) && - (json_last_error() === JSON_ERROR_NONE); + return new Resources\Campaigns; } } diff --git a/src/Resources/Brands.php b/src/Resources/Brands.php index 03da1af..ba94cf1 100644 --- a/src/Resources/Brands.php +++ b/src/Resources/Brands.php @@ -1,6 +1,6 @@ <?php -namespace Coderflex\LaravelSendy\Resources\Resources; +namespace Coderflex\LaravelSendy\Resources; use Coderflex\LaravelSendy\Facades\LaravelSendy; @@ -8,6 +8,6 @@ class Brands { public function get() { - return LaravelSendy::get('/api/brands/get-brands.php'); + return LaravelSendy::post('/api/brands/get-brands.php'); } } diff --git a/src/Resources/Campaigns.php b/src/Resources/Campaigns.php index 3d2f861..b3fb387 100644 --- a/src/Resources/Campaigns.php +++ b/src/Resources/Campaigns.php @@ -1,15 +1,15 @@ <?php -namespace Coderflex\LaravelSendy\Resources\Resources; +namespace Coderflex\LaravelSendy\Resources; -use Coderflex\LaravelSendy\DTOs\CompaignDTO; +use Coderflex\LaravelSendy\DTOs\Campaigns\CampaignDTO; use Coderflex\LaravelSendy\Facades\LaravelSendy; class Campaigns { public function create(array $data) { - $data = CompaignDTO::validateAndCreate($data)->toArray(); + $data = CampaignDTO::validate($data); return LaravelSendy::post('/api/campaigns/create.php', $data); } diff --git a/src/Resources/Lists.php b/src/Resources/Lists.php index 82a5f78..dde78e3 100644 --- a/src/Resources/Lists.php +++ b/src/Resources/Lists.php @@ -1,7 +1,8 @@ <?php -namespace Coderflex\LaravelSendy\Resources\Resources; +namespace Coderflex\LaravelSendy\Resources; +use Coderflex\LaravelSendy\DTOs\Lists\ListsDTO; use Coderflex\LaravelSendy\Facades\LaravelSendy; class Lists @@ -11,13 +12,10 @@ class Lists * * @return array */ - public function get(int $brandId, bool $includeHidden = false) + public function get(array $data, bool $async = false) { - $params = http_build_query([ - 'brand_id' => $brandId, - 'include_hidden' => $includeHidden, - ]); + $data = ListsDTO::validate($data); - return LaravelSendy::get('/api/lists/get-lists.php', $params); + return LaravelSendy::post('/api/lists/get-lists.php', $data, $async); } } diff --git a/src/Resources/Subscribers.php b/src/Resources/Subscribers.php index 10879de..61500b1 100644 --- a/src/Resources/Subscribers.php +++ b/src/Resources/Subscribers.php @@ -1,56 +1,49 @@ <?php -namespace Coderflex\LaravelSendy\Resources\Resources; +namespace Coderflex\LaravelSendy\Resources; -use Coderflex\LaravelSendy\DTOs\SubscribersDTO; +use Coderflex\LaravelSendy\DTOs\Subscribers\DeleteSubscriberDTO; +use Coderflex\LaravelSendy\DTOs\Subscribers\SubscribeDTO; +use Coderflex\LaravelSendy\DTOs\Subscribers\SubscriberStatusDTO; +use Coderflex\LaravelSendy\DTOs\Subscribers\UnsubscribeDTO; use Coderflex\LaravelSendy\Facades\LaravelSendy; class Subscribers { - public function subscribe(array $data) + public function subscribe(array $data, bool $async = false) { - $data = SubscribersDTO::validateAndCreate($data)->toArray(); + $data = SubscribeDTO::validate($data); - return LaravelSendy::post('subscribe', $data); + return LaravelSendy::post('subscribe', $data, $async); } - public function unsubscribe(int $listId, string $email, bool $plainTextResponse) + public function unsubscribe(array $data, bool $async = false) { - $data = http_build_query([ - 'list' => $listId, - 'email' => $email, - 'boolean' => $plainTextResponse, - ]); + $data = UnsubscribeDTO::validate($data); - return LaravelSendy::post('/api/subscribers/unsubscribe.php', $data); + return LaravelSendy::post('api/subscribers/unsubscribe.php', $data, $async); } - public function delete(int $listId, string $email) + public function delete(array $data, bool $async = false) { - $data = http_build_query([ - 'list_id' => $listId, - 'email' => $email, - ]); + $data = DeleteSubscriberDTO::validate($data); - return LaravelSendy::post('/api/subscribers/delete.php', $data); + return LaravelSendy::post('api/subscribers/delete.php', $data, $async); } - public function status(int $listId, string $email) + public function status(array $data, bool $async = false) { - $data = http_build_query([ - 'list_id' => $listId, - 'email' => $email, - ]); + $data = SubscriberStatusDTO::validate($data); - return LaravelSendy::post('/api/subscribers/subscription-status.php', $data); + return LaravelSendy::post('api/subscribers/subscription-status.php', $data, $async); } - public function count(int $listId) + public function count(int $listId, bool $async = false) { - $data = http_build_query([ + $data = [ 'list_id' => $listId, - ]); + ]; - return LaravelSendy::post('/api/subscribers/active-subscriber-count.php', $data); + return LaravelSendy::post('api/subscribers/subscriber-count.php', $data, $async); } } diff --git a/tests/ExampleTest.php b/tests/ExampleTest.php deleted file mode 100644 index 5d36321..0000000 --- a/tests/ExampleTest.php +++ /dev/null @@ -1,5 +0,0 @@ -<?php - -it('can test', function () { - expect(true)->toBeTrue(); -}); diff --git a/tests/Resources/BrandsTest.php b/tests/Resources/BrandsTest.php new file mode 100644 index 0000000..3e98357 --- /dev/null +++ b/tests/Resources/BrandsTest.php @@ -0,0 +1,26 @@ +<?php + +use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Illuminate\Support\Facades\Http; + +beforeEach(function () { + config([ + 'laravel-sendy.api_key' => 'test_api_key', + 'laravel-sendy.api_url' => 'https://sendy.test/', + ]); +}); + +it('can get subscriber brands', function () { + Http::fake([ + 'https://sendy.test/api/brands/get-brands.php' => Http::response([123 => 'Brand Name'], 200), + ]); + + $response = LaravelSendy::brands()->get(); + + expect($response->json())->toBe([123 => 'Brand Name']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/api/brands/get-brands.php' && + $request['api_key'] === 'test_api_key'; + }); +}); diff --git a/tests/Resources/CompaignsTest.php b/tests/Resources/CompaignsTest.php new file mode 100644 index 0000000..cd83292 --- /dev/null +++ b/tests/Resources/CompaignsTest.php @@ -0,0 +1,47 @@ +<?php + +use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Illuminate\Support\Facades\Http; + +beforeEach(function () { + config([ + 'laravel-sendy.api_key' => 'test_api_key', + 'laravel-sendy.api_url' => 'https://sendy.test/', + ]); +}); + +it('can create and send a campaigns', function () { + Http::fake([ + 'https://sendy.test/api/campaigns/create.php' => Http::response(['status' => 'Campaign created and now sending'], 200), + ]); + + $response = LaravelSendy::campaigns()->create([ + 'subject' => 'Test Subject', + 'from_name' => 'John Doe', + 'from_email' => 'john@example.com', + 'reply_to' => 'alex@example.com', + 'title' => 'Test Title', + 'plain_text' => 'This is a plain text version of the email.', + 'html_text' => '<h1>This is a HTML version of the email.</h1>', + 'list_ids' => 'abc123', + 'segment_ids' => 'xyz456', + 'exclude_list_ids' => null, + 'exclude_segment_ids' => null, + 'brand_id' => 'brand123', + 'query_string' => null, + 'track_opens' => 1, + 'track_clicks' => 1, + 'send_campaign' => 1, + 'schedule_date_time' => null, + 'schedule_timezone' => null, + ]); + + expect($response->json())->toBe(['status' => 'Campaign created and now sending']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/api/campaigns/create.php' && + $request['from_email'] === 'john@example.com' && + $request['from_name'] === 'John Doe' && + $request['api_key'] === 'test_api_key'; + }); +}); diff --git a/tests/Resources/EndpointsTest.php b/tests/Resources/EndpointsTest.php new file mode 100644 index 0000000..e0a72b2 --- /dev/null +++ b/tests/Resources/EndpointsTest.php @@ -0,0 +1,32 @@ +<?php + +use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Illuminate\Support\Facades\Http; + +it('throw and exception if the api key not defined', function () { + config([ + 'laravel-sendy.api_key' => null, + 'laravel-sendy.api_url' => 'https://sendy.test', + ]); + + Http::fake([ + 'https://sendy.test/api/brands/get-brands.php' => Http::response(true, 200), + ]); + + $response = LaravelSendy::brands()->get(); + +})->throws(\Coderflex\LaravelSendy\Exceptions\InvalidApiKeyException::class); + +it('throw and exception if the api url not defined', function () { + Http::fake([ + 'https://sendy.test/api/brands/get-brands.php' => Http::response(true, 200), + ]); + + config([ + 'laravel-sendy.api_key' => 'test_api_key', + 'laravel-sendy.api_url' => null, + ]); + + $response = LaravelSendy::brands()->get(); + +})->throws(\Coderflex\LaravelSendy\Exceptions\InvalidApiUrlException::class); diff --git a/tests/Resources/ListsTest.php b/tests/Resources/ListsTest.php new file mode 100644 index 0000000..87a7b6d --- /dev/null +++ b/tests/Resources/ListsTest.php @@ -0,0 +1,31 @@ +<?php + +use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Illuminate\Support\Facades\Http; + +beforeEach(function () { + config([ + 'laravel-sendy.api_key' => 'test_api_key', + 'laravel-sendy.api_url' => 'https://sendy.test/', + ]); +}); + +it('can get subscriber lists', function () { + Http::fake([ + 'https://sendy.test/api/lists/get-lists.php' => Http::response([123 => 'Custom List'], 200), + ]); + + $response = LaravelSendy::lists()->get([ + 'brand_id' => 123, + 'include_hidden' => 'yes', + ]); + + expect($response->json())->toBe([123 => 'Custom List']); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/api/lists/get-lists.php' && + $request['brand_id'] === 123 && + $request['include_hidden'] === 'yes' && + $request['api_key'] === 'test_api_key'; + }); +}); diff --git a/tests/Resources/SubscribersTest.php b/tests/Resources/SubscribersTest.php new file mode 100644 index 0000000..7d7ba94 --- /dev/null +++ b/tests/Resources/SubscribersTest.php @@ -0,0 +1,93 @@ +<?php + +use Coderflex\LaravelSendy\Facades\LaravelSendy; +use Illuminate\Support\Facades\Http; + +beforeEach(function () { + config([ + 'laravel-sendy.api_key' => 'test_api_key', + 'laravel-sendy.api_url' => 'https://sendy.test/', + ]); +}); + +it('can subscribe a user', function () { + Http::fake([ + 'https://sendy.test/subscribe' => Http::response(true, 200), + ]); + + $response = LaravelSendy::subscribers()->subscribe([ + 'name' => 'John Doe', + 'email' => 'john@example.com', + 'list' => 'abc123', + 'country' => 'UAE', + ]); + + expect($response->json())->toBe(1); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/subscribe' && + $request['email'] === 'john@example.com' && + $request['list'] === 'abc123' && + $request['api_key'] === 'test_api_key'; + }); +}); + +it('can unsubscribe a user', function () { + Http::fake([ + 'https://sendy.test/api/subscribers/unsubscribe.php' => Http::response(true, 200), + ]); + + $response = LaravelSendy::subscribers()->unsubscribe([ + 'list' => 123, + 'email' => 'jane@example.com', + 'boolean' => true, + ]); + + expect($response->json())->toBe(1); + + Http::assertSent(function ($request) { + return $request->url() === 'https://sendy.test/api/subscribers/unsubscribe.php' && + $request['email'] === 'jane@example.com' && + $request['list'] === 123; + }); +}); + +it('can delete a subscriber', function () { + Http::fake([ + 'https://sendy.test/api/subscribers/delete.php' => Http::response(true, 200), + ]); + + $response = LaravelSendy::subscribers()->delete([ + 'list_id' => 123, + 'email' => 'john@example.com', + ]); + + expect($response->json())->toBe(1); + + Http::assertSent(fn ($request) => $request['email'] === 'john@example.com' && + $request['list_id'] === 123 + ); +}); + +it('can get subscriber status', function () { + Http::fake([ + 'https://sendy.test/api/subscribers/subscription-status.php' => Http::response(['status' => 'Subscribed'], 200), + ]); + + $response = LaravelSendy::subscribers()->status([ + 'list_id' => 123, + 'email' => 'john@example.com', + ]); + + expect($response->json())->toBe(['status' => 'Subscribed']); +}); + +it('can get subscriber count', function () { + Http::fake([ + 'https://sendy.test/api/subscribers/subscriber-count.php' => Http::response(25, 200), + ]); + + $response = LaravelSendy::subscribers()->count(123); + + expect($response->json())->toBe(25); +});