diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f65db30..8d166b2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -19,13 +19,13 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: 8.3 + php-version: 8.2 - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - - name: Run PHPUnit - run: vendor/bin/phpunit - - name: Run PHPStan - run: vendor/bin/phpstan analyze -l 6 src \ No newline at end of file + run: composer phpstan + + - name: Run PHPUnit + run: composer test \ No newline at end of file diff --git a/composer.json b/composer.json index 31496ac..b3c82d0 100644 --- a/composer.json +++ b/composer.json @@ -11,8 +11,9 @@ } ], "require": { + "php": "^8.2", "guzzlehttp/guzzle": "^6.0|^7.0", - "php": "^8.3" + "saloonphp/saloon": "^3.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.0|^3.14", @@ -34,7 +35,7 @@ "test": "./vendor/bin/phpunit", "test-coverage": "XDEBUG_MODE=coverage ./vendor/bin/phpunit --coverage-html .coverage", "fix": "PHP_CS_FIXER_IGNORE_ENV=1 ./vendor/bin/php-cs-fixer fix --verbose --config=.php-cs-fixer.php", - "phpstan": "./vendor/bin/phpstan analyse src" + "phpstan": "./vendor/bin/phpstan analyze -l 8 src" }, "config": { "sort-packages": true diff --git a/src/Contracts/EventResourceContract.php b/src/Contracts/EventResourceContract.php new file mode 100644 index 0000000..6656abe --- /dev/null +++ b/src/Contracts/EventResourceContract.php @@ -0,0 +1,14 @@ + $data + */ + public function create(array $data): Response; + + /** + * @param array $data + */ + public function update(string $code, array $data): Response; +} diff --git a/src/CoreApi.php b/src/CoreApi.php index 9505762..2565abd 100644 --- a/src/CoreApi.php +++ b/src/CoreApi.php @@ -4,92 +4,44 @@ namespace AirLST\SdkPhp; -use GuzzleHttp\Client; -use GuzzleHttp\Exception\ClientException; -use GuzzleHttp\HandlerStack; +use AirLST\SdkPhp\Resources\EventResource; +use AirLST\SdkPhp\Resources\GuestResource; +use Saloon\Http\Connector; -class CoreApi +class CoreApi extends Connector { - public string $baseURL = 'https://airlst.app'; - public string $apiKey; - public string $locale = 'de-DE'; - public string $eventId; - protected ?HandlerStack $handler = null; + protected string $baseUrl = 'https://airlst.app/api'; - public function setBaseURL(string $baseURL): self - { - $this->baseURL = $baseURL; + public function __construct(protected readonly string $apiKey) {} - return $this; + public function resolveBaseUrl(): string + { + return $this->baseUrl; } - public function setApiKey(string $apiKey): self + public function setBaseUrl(string $baseUrl): void { - $this->apiKey = $apiKey; - - return $this; + $this->baseUrl = $baseUrl; } - public function setLocale(string $locale): self + public function event(): EventResource { - $this->locale = $locale; - - return $this; + return new EventResource($this); } - public function setEventId(string $eventId): self + public function guest(string $eventId): GuestResource { - $this->eventId = $eventId; + $this->baseUrl = $this->baseUrl . "/events/{$eventId}/guests"; - return $this; + return new GuestResource($this); } - /** - * @return array - */ - public function getRequestHeaders(): array + protected function defaultHeaders(): array { return [ - 'content-type' => 'application/json', - 'accept' => 'application/json', - 'x-api-key' => $this->apiKey, - 'accept-language' => $this->locale, + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'X-Api-Key' => $this->apiKey ]; } - - public function setHandler(HandlerStack $handler): void - { - $this->handler = $handler; - } - - /** - * @param string $uri - * @param RequestMethod $method - * @param array $options - * @return array - */ - public function send( - string $uri, - RequestMethod $method = RequestMethod::GET, - array $options = [] - ): array - { - try { - $client = new Client([ - 'base_uri' => $this->baseURL, - 'headers' => $this->getRequestHeaders(), - 'handler' => $this->handler - ]); - - $response = $client->request($method->value, "/api$uri", $options); - - return json_decode((string) $response->getBody(), true); - } catch (ClientException $exception) { - throw new \RuntimeException( - $exception->getResponse()->getBody()->getContents(), - $exception->getCode(), - $exception - ); - } - } } \ No newline at end of file diff --git a/src/RequestMethod.php b/src/RequestMethod.php deleted file mode 100644 index b1945c0..0000000 --- a/src/RequestMethod.php +++ /dev/null @@ -1,13 +0,0 @@ -eventId; + } +} \ No newline at end of file diff --git a/src/Requests/Event/ListRequest.php b/src/Requests/Event/ListRequest.php new file mode 100644 index 0000000..a548eff --- /dev/null +++ b/src/Requests/Event/ListRequest.php @@ -0,0 +1,18 @@ + $data + */ + public function __construct(protected array $data) {} + + public function resolveEndpoint(): string + { + return '/'; + } + + /** + * @return array + */ + protected function defaultBody(): array + { + return $this->data; + } +} \ No newline at end of file diff --git a/src/Requests/Guest/GetRequest.php b/src/Requests/Guest/GetRequest.php new file mode 100644 index 0000000..da528b6 --- /dev/null +++ b/src/Requests/Guest/GetRequest.php @@ -0,0 +1,20 @@ +code; + } +} \ No newline at end of file diff --git a/src/Requests/Guest/UpdateRequest.php b/src/Requests/Guest/UpdateRequest.php new file mode 100644 index 0000000..c963513 --- /dev/null +++ b/src/Requests/Guest/UpdateRequest.php @@ -0,0 +1,35 @@ + $data + */ + public function __construct(protected string $code, protected array $data) {} + + public function resolveEndpoint(): string + { + return '/' . $this->code; + } + + /** + * @return array + */ + protected function defaultBody(): array + { + return $this->data; + } +} \ No newline at end of file diff --git a/src/Requests/Guest/ValidateCodeRequest.php b/src/Requests/Guest/ValidateCodeRequest.php new file mode 100644 index 0000000..ff7a31c --- /dev/null +++ b/src/Requests/Guest/ValidateCodeRequest.php @@ -0,0 +1,34 @@ + + */ + protected function defaultBody(): array + { + return [ + 'code' => $this->code, + ]; + } +} \ No newline at end of file diff --git a/src/Resources/Event.php b/src/Resources/Event.php deleted file mode 100644 index bf82ade..0000000 --- a/src/Resources/Event.php +++ /dev/null @@ -1,16 +0,0 @@ - - */ - public function get(): array - { - return $this->send("/events/{$this->eventId}"); - } -} \ No newline at end of file diff --git a/src/Resources/EventResource.php b/src/Resources/EventResource.php new file mode 100644 index 0000000..eef3112 --- /dev/null +++ b/src/Resources/EventResource.php @@ -0,0 +1,24 @@ +connector->send(new ListRequest()); + } + + public function get(string $eventId): Response + { + return $this->connector->send(new GetRequest($eventId)); + } +} \ No newline at end of file diff --git a/src/Resources/Guest.php b/src/Resources/Guest.php deleted file mode 100644 index 2eeffbb..0000000 --- a/src/Resources/Guest.php +++ /dev/null @@ -1,62 +0,0 @@ -send( - uri: "{$this->getEventURL()}/guests/validate-code", - method: RequestMethod::POST, - options: ['form_params' => ['code' => $code]] - ); - } - - /** - * @return array{'data': array} - */ - public function get(string $code): array - { - return $this->send("{$this->getEventURL()}/guests/{$code}"); - } - - /** - * @param array $data - * @return array{'data': array} - */ - public function create(array $data): array - { - return $this->send( - uri: "{$this->getEventURL()}/guests", - method: RequestMethod::POST, - options: ['form_params' => $data] - ); - } - - /** - * @param array $data - * @return array{'data': array} - */ - public function update(string $code, array $data): array - { - return $this->send( - uri: "{$this->getEventURL()}/guests/{$code}", - method: RequestMethod::PUT, - options: ['form_params' => $data] - ); - } - - public function getEventURL(): string - { - return "/events/{$this->eventId}"; - } -} diff --git a/src/Resources/GuestResource.php b/src/Resources/GuestResource.php new file mode 100644 index 0000000..b1956db --- /dev/null +++ b/src/Resources/GuestResource.php @@ -0,0 +1,36 @@ +connector->send(new ValidateCodeRequest($code)); + } + + public function get(string $code): Response + { + return $this->connector->send(new GetRequest($code)); + } + + public function create(array $data): Response + { + return $this->connector->send(new CreateRequest($data)); + } + + public function update(string $code, array $data): Response + { + return $this->connector->send(new UpdateRequest($code, $data)); + } +} diff --git a/tests/CoreApiTest.php b/tests/CoreApiTest.php index 6b23394..841ebf2 100644 --- a/tests/CoreApiTest.php +++ b/tests/CoreApiTest.php @@ -4,81 +4,30 @@ namespace AirLST\SdkPhp\Tests; -use AirLST\SdkPhp\CoreApi; -use AirLST\SdkPhp\RequestMethod; +use AirLST\SdkPhp\CoreAPI; +use AirLST\SdkPhp\Resources\EventResource; +use AirLST\SdkPhp\Resources\GuestResource; -class CoreApiTest extends TestCase +class CoreAPITest extends TestCase { - public function testSetBaseURL(): void - { - $this->api->setBaseURL('https://airlst.test'); - - $this->assertEquals( - 'https://airlst.test', - $this->api->baseURL - ); - } - - public function testSetEventId(): void - { - $this->api->setEventId('event-id'); - - $this->assertEquals('event-id', $this->api->eventId); - } - - public function testSetApiKey(): void - { - $this->api->setApiKey('api-key'); - - $this->assertEquals('api-key', $this->api->apiKey); - } + protected CoreAPI $core; - public function testSetLocale(): void + public function testResolveBaseUrl(): void { - $this->api->setLocale('en-US'); + $baseUrl = 'https://staging.airlst.app/api'; - $this->assertEquals('en-US', $this->api->locale); - } - - public function testGetEventUrl(): void - { - $this->api->setEventId('event-id'); + $this->core->setBaseUrl($baseUrl); - $this->assertEquals('event-id', $this->api->eventId); + $this->assertEquals($baseUrl, $this->core->resolveBaseUrl()); } - public function testGetRequestHeaders(): void + public function testEvent(): void { - $this->api->setApiKey('api-key')->setLocale('en-GB'); - - $this->assertEquals([ - 'content-type' => 'application/json', - 'accept' => 'application/json', - 'x-api-key' => 'api-key', - 'accept-language' => 'en-GB', - ], $this->api->getRequestHeaders()); - } - - public function testSendSuccessful(): void - { - $expects = ['data' => [ - 'event' => [ - 'uuid' => 'event-uuid', - ] - ]]; - - $this->fake($expects); - - $this->api->setApiKey('api-key'); - $this->api->setEventId('event-id'); - - $response = $this->api->send('/events/event-uuid', RequestMethod::GET); - - $this->assertEquals($expects, $response); + $this->assertInstanceOf(EventResource::class, $this->core->event()); } - protected function setUp(): void + public function testGuest(): void { - $this->api = new CoreApi(); + $this->assertInstanceOf(GuestResource::class, $this->core->guest('event-id')); } } \ No newline at end of file diff --git a/tests/Requests/Event/GetRequestTest.php b/tests/Requests/Event/GetRequestTest.php new file mode 100644 index 0000000..d3c463a --- /dev/null +++ b/tests/Requests/Event/GetRequestTest.php @@ -0,0 +1,26 @@ +assertSame('/events/event-id', $request->resolveEndpoint()); + } + + public function testGet(): void + { + $mockClient = $this->mock(GetRequest::class); + + $this->core->withMockClient($mockClient)->send(new GetRequest('event-id')); + + $mockClient->assertSent(GetRequest::class); + } +} diff --git a/tests/Requests/Event/ListRequestTest.php b/tests/Requests/Event/ListRequestTest.php new file mode 100644 index 0000000..e5e8935 --- /dev/null +++ b/tests/Requests/Event/ListRequestTest.php @@ -0,0 +1,20 @@ +mock(ListRequest::class); + + $this->core->withMockClient($mockClient)->send(new ListRequest()); + + $mockClient->assertSent(ListRequest::class); + } +} diff --git a/tests/Requests/Guest/CreateRequestTest.php b/tests/Requests/Guest/CreateRequestTest.php new file mode 100644 index 0000000..af06642 --- /dev/null +++ b/tests/Requests/Guest/CreateRequestTest.php @@ -0,0 +1,20 @@ +mock(CreateRequest::class); + + $this->core->withMockClient($mockClient)->send(new CreateRequest(['status' => 'confirmed'])); + + $mockClient->assertSent(CreateRequest::class); + } +} diff --git a/tests/Requests/Guest/GetRequestTest.php b/tests/Requests/Guest/GetRequestTest.php new file mode 100644 index 0000000..ae97ed5 --- /dev/null +++ b/tests/Requests/Guest/GetRequestTest.php @@ -0,0 +1,20 @@ +mock(GetRequest::class); + + $this->core->withMockClient($mockClient)->send(new GetRequest('event-id')); + + $mockClient->assertSent(GetRequest::class); + } +} diff --git a/tests/Requests/Guest/UpdateRequestTest.php b/tests/Requests/Guest/UpdateRequestTest.php new file mode 100644 index 0000000..f454bcd --- /dev/null +++ b/tests/Requests/Guest/UpdateRequestTest.php @@ -0,0 +1,20 @@ +mock(UpdateRequest::class); + + $this->core->withMockClient($mockClient)->send(new UpdateRequest('xyz', ['status' => 'confirmed'])); + + $mockClient->assertSent(UpdateRequest::class); + } +} diff --git a/tests/Requests/Guest/ValidateCodeRequestTest.php b/tests/Requests/Guest/ValidateCodeRequestTest.php new file mode 100644 index 0000000..6c983cf --- /dev/null +++ b/tests/Requests/Guest/ValidateCodeRequestTest.php @@ -0,0 +1,21 @@ +mock(ValidateCodeRequest::class); + + $this->core->withMockClient($mockClient)->send(new ValidateCodeRequest('event-id')); + + $mockClient->assertSent(ValidateCodeRequest::class); + } +} diff --git a/tests/Resources/EventResourceTest.php b/tests/Resources/EventResourceTest.php new file mode 100644 index 0000000..0cd1be7 --- /dev/null +++ b/tests/Resources/EventResourceTest.php @@ -0,0 +1,49 @@ + ['events' => []]]; + + $mockClient = $this->mock(ListRequest::class, $expects); + + $resource = $this->resource($this->core->withMockClient($mockClient)); + $result = $resource->list(); + + $mockClient->assertSent( + fn (Request $request, Response $response) => $result->body() === $response->body() + ); + } + + public function testGet(): void + { + $expects = ['data' => ['event' => ['id' => 'event-id']]]; + + $mockClient = $this->mock(GetRequest::class, $expects); + + $resource = $this->resource($this->core->withMockClient($mockClient)); + $result = $resource->get($expects['data']['event']['id']); + + $mockClient->assertSent( + fn (Request $request, Response $response) => $result->body() === $response->body() + ); + } + + protected function resource(CoreApi $core): EventResource + { + return new EventResource($core); + } +} \ No newline at end of file diff --git a/tests/Resources/EventTest.php b/tests/Resources/EventTest.php deleted file mode 100644 index 99739cd..0000000 --- a/tests/Resources/EventTest.php +++ /dev/null @@ -1,41 +0,0 @@ - [ - ['name' => 'event-name'] - ]]; - - $this->fake($expects); - - $response = $this->api->get(); - - $this->assertEquals($expects, $response); - $this->assertCount(count($expects['data']), $response); - } - - public function testGet(): void - { - $expects = ['data' => ['name' => 'event-name']]; - - $this->fake($expects); - - $this->assertEquals($expects, $this->api->get()); - } - - protected function setUp(): void - { - $this->api = new Event(); - $this->api->setApiKey('api-key'); - $this->api->setEventId('event-id'); - } -} diff --git a/tests/Resources/GuestResourceTest.php b/tests/Resources/GuestResourceTest.php new file mode 100644 index 0000000..da69571 --- /dev/null +++ b/tests/Resources/GuestResourceTest.php @@ -0,0 +1,79 @@ +mock(ValidateCodeRequest::class, ['data' => ['valid' => true]]); + + $resource = $this->resource($this->core->withMockClient($mockClient)); + $result = $resource->validateCode('xyz'); + + $mockClient->assertSent( + fn (Request $request, Response $response) => + $request instanceof ValidateCodeRequest && $result->body() === $response->body() + ); + } + + public function testGet(): void + { + $mockClient = $this->mock(GetRequest::class, ['data' => ['code' => 'xyz']]); + + $resource = $this->resource($this->core->withMockClient($mockClient)); + $result = $resource->get('xyz'); + + $mockClient->assertSent( + fn (Request $request, Response $response) => + $request instanceof GetRequest && $result->body() === $response->body() + ); + } + + public function testCreate(): void + { + $mockClient = $this->mock(CreateRequest::class, ['data' => ['status' => 'confirmed']]); + + $resource = $this->resource($this->core->withMockClient($mockClient)); + $result = $resource->create([ + 'name' => 'John Lennon', + 'email' => 'j.lennon@sdk.com', + 'status' => 'confirmed' + ]); + + $mockClient->assertSent( + fn (Request $request, Response $response) => + $request instanceof CreateRequest && $result->body() === $response->body() + ); + } + + public function testUpdate(): void + { + $mockClient = $this->mock(UpdateRequest::class, ['data' => ['status' => 'confirmed']]); + + $resource = $this->resource($this->core->withMockClient($mockClient)); + $result = $resource->update('xyz', ['status' => 'confirmed']); + + $mockClient->assertSent( + fn (Request $request, Response $response) => + $request instanceof UpdateRequest && $result->body() === $response->body() + ); + } + + protected function resource(CoreApi $core): GuestResource + { + return new GuestResource($core); + } +} \ No newline at end of file diff --git a/tests/Resources/GuestTest.php b/tests/Resources/GuestTest.php deleted file mode 100644 index ec200e1..0000000 --- a/tests/Resources/GuestTest.php +++ /dev/null @@ -1,70 +0,0 @@ - ['code' => 'guest-code']]; - - $this->fake($expects); - - $this->assertEquals($expects, $this->api->validateCode('guest-code')); - } - - public function testGet(): void - { - $expects = ['data' => ['code' => 'guest-code']]; - - $this->fake($expects); - - $this->assertEquals($expects, $this->api->get('guest-code')); - } - - public function testCreate(): void - { - $expects = [ - 'data' => [ - 'contact' => ['first_name' => 'John Lennon'] - ] - ]; - - $this->fake($expects); - - $this->assertEquals($expects, $this->api->create($expects)); - } - - public function testUpdate(): void - { - $code = 'guest-code'; - $expects = [ - 'data' => [ - 'contact' => ['first_name' => 'John Lennon'] - ] - ]; - - $this->fake($expects); - - $this->assertEquals($expects, $this->api->update($code, $expects)); - } - - public function testGetEventURL(): void - { - $this->api->setEventId('event-id'); - - $this->assertEquals('/events/event-id', $this->api->getEventURL()); - } - - protected function setUp(): void - { - $this->api = new Guest(); - $this->api->setApiKey('api-key'); - $this->api->setEventId('event-id'); - } -} diff --git a/tests/TestCase.php b/tests/TestCase.php index cb53830..f8a0d67 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -4,22 +4,29 @@ namespace AirLST\SdkPhp\Tests; -use AirLST\SdkPhp\CoreApi; -use AirLST\SdkPhp\Resources\Event; -use AirLST\SdkPhp\Resources\Guest; -use GuzzleHttp\Handler\MockHandler; -use GuzzleHttp\HandlerStack; -use GuzzleHttp\Psr7\Response; use PHPUnit\Framework\TestCase as BaseTestCase; +use Saloon\Http\Faking\MockClient; +use Saloon\Http\Faking\MockResponse; +use Saloon\Config; class TestCase extends BaseTestCase { - protected CoreApi|Event|Guest $api; + protected \AirLST\SdkPhp\CoreApi $core; - protected function fake(array $expects, ?int $status = 200): void + public function mock(string $class, array $expects = []): MockClient { - $this->api->setHandler(HandlerStack::create( - new MockHandler([new Response($status, [], json_encode($expects))]) - )); + return new MockClient([ + $class => MockResponse::make($expects, 200), + ]); + } + + protected function setUp(): void + { + parent::setUp(); + + MockClient::destroyGlobal(); + Config::preventStrayRequests(); + + $this->core = new \AirLST\SdkPhp\CoreApi('api-key'); } } \ No newline at end of file