Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support venue search requests #8

Merged
merged 2 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
### Unreleased

### v2.3.0 (2024-11-28)

* Add support for venue search endpoint
* Remove CI config from export package

### v2.2.0 (2024-10-01)

* Support PHP 8.3
Expand Down
29 changes: 29 additions & 0 deletions src/FestivalsApiClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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, '/');
Expand Down
34 changes: 34 additions & 0 deletions src/Result/VenueSearchResult.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php
/**
* @author Festivals Edinburgh <[email protected]>
* @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;
}
}
54 changes: 37 additions & 17 deletions test/unit/FestivalsApiClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,24 @@ 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);
$this->expectExceptionMessage('Missing credentials');
$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
Expand Down Expand Up @@ -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'])]
Expand All @@ -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])]
Expand All @@ -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
Expand All @@ -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],
Expand All @@ -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, '<p>HTML</p>', 'API responded with invalid JSON'])]
#[TestWith([200, '', 'API responded with invalid JSON'])]
#[TestWith([404, '{"error":"Event not found"}', 'Event not found'])]
Expand All @@ -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()
);
Expand Down
Loading