From 201eb5f60ae5b715550735fb84aa052a8626a22c Mon Sep 17 00:00:00 2001 From: craig410 Date: Thu, 28 Nov 2024 12:12:43 +0000 Subject: [PATCH 1/2] Remove CI config from export package --- .gitattributes | 4 ++-- CHANGELOG.md | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index 0fe1f3d..e7733b4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -29,7 +29,7 @@ README text eol=lf *.sqlite binary # Ignore paths that should not be included in an archive (eg for a distribution version) +/.github export-ignore /test export-ignore -phpunit.xml export-ignore -.travis.yml export-ignore +/phpunit.xml export-ignore src/ClientVersion.php export-subst diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fb8a79..3b34fa4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ### Unreleased +### v2.3.0 (2024-11-28) + +* Remove CI config from export package + ### v2.2.0 (2024-10-01) * Support PHP 8.3 From 96ac2dd12669463b2ad832d597c3bbb6c1c2c8d3 Mon Sep 17 00:00:00 2001 From: craig410 Date: Thu, 28 Nov 2024 12:12:59 +0000 Subject: [PATCH 2/2] Add support for venue search endpoint --- CHANGELOG.md | 1 + src/FestivalsApiClient.php | 29 +++++++++++++++ src/Result/VenueSearchResult.php | 34 ++++++++++++++++++ test/unit/FestivalsApiClientTest.php | 54 +++++++++++++++++++--------- 4 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 src/Result/VenueSearchResult.php diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b34fa4..920f23b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ### v2.3.0 (2024-11-28) +* Add support for venue search endpoint * Remove CI config from export package ### v2.2.0 (2024-10-01) diff --git a/src/FestivalsApiClient.php b/src/FestivalsApiClient.php index 98e4c9d..c288620 100644 --- a/src/FestivalsApiClient.php +++ b/src/FestivalsApiClient.php @@ -10,17 +10,20 @@ use FestivalsApi\Result\EventSearchResult; use FestivalsApi\Result\SingleEventResult; +use FestivalsApi\Result\VenueSearchResult; use GuzzleHttp\Client; use GuzzleHttp\Exception\BadResponseException; use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Psr7\Request; use InvalidArgumentException; use Psr\Http\Message\ResponseInterface; +use function http_build_query; class FestivalsApiClient { const BASE_URL = 'https://api.edinburghfestivalcity.com'; const EVENTS_ENDPOINT = '/events'; + const VENUES_ENDPOINT = '/venues'; protected string $access_key; @@ -73,6 +76,32 @@ public function searchEvents(array $query): EventSearchResult return new EventSearchResult($events, (string) $request->getUri(), $total_results); } + /** + * Search API for venues matching query + * + * @throws FestivalsApiClientException + * @throws GuzzleException + */ + public function searchVenues(array $query): VenueSearchResult + { + $this->throwEmptyCredentials(); + + $url = self::VENUES_ENDPOINT; + if ( ! empty($query)) { + $url .= '?'.http_build_query($query); + } + + $request = $this->createRequest($url); + $response = $this->sendRequest($request); + + return new VenueSearchResult( + venues: $this->decodeJsonResponse($response), + url: $request->getUri(), + total_results: (int) $response->getHeaderLine('x-total-results') ?: 0 + ); + } + + public function setBaseUrl(string $base_url): void { $this->base_url = rtrim($base_url, '/'); diff --git a/src/Result/VenueSearchResult.php b/src/Result/VenueSearchResult.php new file mode 100644 index 0000000..37d25b8 --- /dev/null +++ b/src/Result/VenueSearchResult.php @@ -0,0 +1,34 @@ + + * @licence BSD-3-Clause + */ + + +namespace FestivalsApi\Result; + + +class VenueSearchResult +{ + public function __construct( + protected array $venues, + protected string $url, + protected int $total_results + ) { + } + + public function getVenues(): array + { + return $this->venues; + } + + public function getTotalResults(): int + { + return $this->total_results; + } + + public function getUrl(): string + { + return $this->url; + } +} diff --git a/test/unit/FestivalsApiClientTest.php b/test/unit/FestivalsApiClientTest.php index 28e5f2b..9d2765a 100644 --- a/test/unit/FestivalsApiClientTest.php +++ b/test/unit/FestivalsApiClientTest.php @@ -33,7 +33,8 @@ public function test_it_is_initialisable(): void #[TestWith(['loadEvent', 1234])] #[TestWith(['searchEvents', ['title' => 'Foo']])] - public function test_it_throws_if_you_attempt_to_use_it_without_setting_credentials(string $method, $args): void + #[TestWith(['searchVenues', ['name' => 'Theatre']])] + public function test_it_throws_if_you_attempt_to_use_it_without_setting_credentials(string $method, mixed $args): void { $subject = $this->newSubject(); $this->expectException(FestivalsApiClientException::class); @@ -41,16 +42,15 @@ public function test_it_throws_if_you_attempt_to_use_it_without_setting_credenti $subject->$method($args); } - public function test_it_performs_requests_with_initialised_key_and_secret(): void + #[TestWith(['searchEvents', 'key=mykey&signature=9c638d91ba50f39da6ecc1d4e8846ae30c318f55'])] + #[TestWith(['searchVenues', 'key=mykey&signature=71a425569de5646f9d42d73d1f1ba524fe5e9051'])] + public function test_it_performs_requests_with_initialised_key_and_secret(string $method, string $expect): void { $this->mockGuzzleWithEmptySuccessResponse(); $subject = $this->newSubject(); $subject->setCredentials('mykey', 'mysecret'); - $subject->searchEvents([]); - $this->assertSame( - 'key=mykey&signature=9c638d91ba50f39da6ecc1d4e8846ae30c318f55', - $this->getRequest(0)->getUri()->getQuery() - ); + $subject->$method([]); + $this->assertSame($expect, $this->getRequest(0)->getUri()->getQuery()); } public static function provider_constructor_base_urls(): array @@ -105,20 +105,20 @@ public function test_it_calls_the_api_with_the_event_id_specified(): void $result = $subject->loadEvent('1234'); // calls the API only once - $this->assertEquals(1, count($this->history)); + $this->assertSame(1, count($this->history)); $expected = 'https://api.edinburghfestivalcity.com/events/1234?key=test-key&signature=93604dba44ed6a988bb6b25f480c66f1d7978ec1'; // it calls the API correctly - $this->assertEquals($expected, (string) $this->getRequest(0)->getUri()); + $this->assertSame($expected, (string) $this->getRequest(0)->getUri()); //it records the same url in the result object - $this->assertEquals($expected, $result->getUrl()); + $this->assertSame($expected, $result->getUrl()); } public function test_load_event_returns_single_event_from_response(): void { $this->mockGuzzleWithResponse(new Response(200, [], '{"title": "Foo Bar", "id":4321}')); $subject = $this->newSubjectWithValidCredentials(); - $this->assertEquals($subject->loadEvent('4321')->getEvent(), ['title' => 'Foo Bar', 'id' => 4321]); + $this->assertSame($subject->loadEvent('4321')->getEvent(), ['title' => 'Foo Bar', 'id' => 4321]); } #[TestWith([['title' => "\"Foo Bar\""], 'title=%22Foo+Bar%22&key=test-key&signature=b313169e84c3922f07c6010e2191486a5192c039'])] @@ -132,9 +132,9 @@ public function test_it_correctly_url_encodes_search_query(array $query, string $expected = 'https://api.edinburghfestivalcity.com/events?'.$expected; //it queries the API with the correct URL - $this->assertEquals($expected, (string) $this->getRequest(0)->getUri()); + $this->assertSame($expected, (string) $this->getRequest(0)->getUri()); //it records the same url in the result object - $this->assertEquals($expected, $result->getUrl()); + $this->assertSame($expected, $result->getUrl()); } #[TestWith([[], 0])] @@ -145,7 +145,7 @@ public function test_event_search_result_holds_total_result_count_from_header(ar $this->mockGuzzleWithResponse(new Response(200, $headers, "[]")); $subject = $this->newSubjectWithValidCredentials(); $result = $subject->searchEvents([]); - $this->assertEquals($expected, $result->getTotalResults()); + $this->assertSame($expected, $result->getTotalResults()); } public function test_search_event_returns_events_from_response(): void @@ -159,7 +159,7 @@ public function test_search_event_returns_events_from_response(): void ); $subject = $this->newSubjectWithValidCredentials(); - $this->assertEquals( + $this->assertSame( $subject->searchEvents(['title' => 'Test'])->getEvents(), [ ['title' => 'Test event 1', 'id' => 101], @@ -168,6 +168,26 @@ public function test_search_event_returns_events_from_response(): void ); } + public function test_search_venue_returns_events_from_response(): void + { + $this->mockGuzzleWithResponse( + new Response( + 200, + [], + '[{"name": "Theatre", "id":101},{"name": "Studio", "id":102}]' + ) + ); + + $subject = $this->newSubjectWithValidCredentials(); + $this->assertSame( + $subject->searchVenues(['title' => 'Test'])->getVenues(), + [ + ['name' => 'Theatre', 'id' => 101], + ['name' => 'Studio', 'id' => 102], + ] + ); + } + #[TestWith([200, '

HTML

', 'API responded with invalid JSON'])] #[TestWith([200, '', 'API responded with invalid JSON'])] #[TestWith([404, '{"error":"Event not found"}', 'Event not found'])] @@ -192,8 +212,8 @@ public function test_client_exception_contains_url_requested(): void try { $subject->searchEvents([]); } catch (FestivalsApiClientException $e) { - $this->assertEquals('Something went wrong', $e->getMessage()); - $this->assertEquals( + $this->assertSame('Something went wrong', $e->getMessage()); + $this->assertSame( "https://api.edinburghfestivalcity.com/events?key=test-key&signature=7793ae3038669f76de954f197f1818727a12a037", $e->getUrl() );