Skip to content

Commit

Permalink
22: Resolves middleware not working after hours of troubleshooting gu…
Browse files Browse the repository at this point in the history
…zzle's opaque error handling
  • Loading branch information
mradcliffe committed Jun 19, 2024
1 parent c85e543 commit 6e48e75
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 45 deletions.
45 changes: 32 additions & 13 deletions src/XeroClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -89,22 +89,24 @@ public function getConnections(): array

/**
* {@inheritdoc}
*
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
*/
public static function createFromConfig(array $config, array $options = []): static
{
if (isset($options['tenant'])) {
$config['headers']['xero-tenant-id'] = $options['tenant'];
if (!isset($config['Accept'])) {
$config['Accept'] = 'application/json';
}

if (!isset($config['base_uri'])) {
$config['base_uri'] = 'https://api.xero.com/api.xro/2.0/';
}

if (isset($config['handler']) && is_a($config['handler'], '\GuzzleHttp\HandlerStack')) {
$stack = $config['handler'];
$stack = HandlerStack::create($config['handler']);
} else {
$stack = HandlerStack::create();
}

$stack->push(Middleware::mapRequest(function (RequestInterface $request) use ($options) {
$stack->before('prepare_body', Middleware::mapRequest(function (RequestInterface $request) use ($options) {
$validUrls = array_filter(self::getValidUrls(), fn ($url) => str_starts_with($request->getUri(), $url));
if (empty($validUrls)) {
throw new XeroRequestException('API URL is not valid', $request);
Expand All @@ -113,8 +115,13 @@ public static function createFromConfig(array $config, array $options = []): sta
if (!isset($options['auth_token'])) {
throw new XeroRequestException('Missing required parameter auth_token', $request);
}
return $request->withHeader('Authorization', 'Bearer ' . $options['auth_token']);
}));
$new = $request->withHeader('Authorization', 'Bearer ' . $options['auth_token']);

if (isset($options['tenant'])) {
$new = $new->withHeader('Xero-tenant-id', $options['tenant']);
}
return $new;
}), 'xero');

$client = new Client($config + [
'handler' => $stack,
Expand All @@ -126,7 +133,6 @@ public static function createFromConfig(array $config, array $options = []): sta
* {@inheritdoc}
*
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
*/
public static function createFromToken(
string $id,
Expand All @@ -138,6 +144,8 @@ public static function createFromToken(
array $collaborators = [],
string $redirectUri = ''
): static {
$guzzle_options = [];
$xero_options = [];
if ($grant !== null) {
// Fetch a new access token from a refresh token.
$provider = new XeroProvider([
Expand All @@ -157,14 +165,20 @@ public static function createFromToken(
$token = $refreshedToken->getToken();
}

$guzzle_options['Accept'] = 'application/json';
if (isset($options['handler'])) {
$guzzle_options['handler'] = $options['handler'];
}
if (!isset($options['base_uri'])) {
$options['base_uri'] = 'https://api.xero.com/api.xro/2.0/';
$guzzle_options['base_uri'] = 'https://api.xero.com/api.xro/2.0/';
}
if (isset($options['tenant'])) {
$xero_options['tenant'] = $options['tenant'];
}
$xero_options['auth_token'] = $token;

// Create a new static instance.
$instance = self::createFromConfig($options, ['auth_token' => $token]);

$instance->tenantIds = $instance->getConnections();
$instance = self::createFromConfig($guzzle_options, $xero_options);

if (isset($refreshedToken)) {
$instance->refreshedToken = $refreshedToken;
Expand Down Expand Up @@ -226,10 +240,15 @@ public function getRefreshedToken(): ?AccessTokenInterface
/**
* The tenant guids accessible by this client.
*
* This will make a request if tenant ids is empty.
*
* @return string[]
*/
public function getTenantIds(): array
{
if (!$this->tenantIds) {
$this->tenantIds = $this->getConnections();
}
return $this->tenantIds;
}
}
37 changes: 13 additions & 24 deletions tests/src/XeroClientOAuth2Test.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public function testCreateFromTokenError(): void
'instance' => $this->createGuid(),
])),
]);
$options = ['handler' => new HandlerStack($mock)];
$options = ['handler' => HandlerStack::create($mock)];
$httpClient = new Client($options);
$this->expectException(IdentityProviderException::class);

Expand All @@ -60,23 +60,23 @@ public function testCreateFromTokenError(): void
* Tests creating from a refresh token.
*
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
*/
public function testCreateFromRefreshToken(): void
{
$expected = [
[
'id' => $this->createGuid(),
'tenantId' => $this->createGuid(),
'tenantType' => 'ORGANISATION',
],
];
$token = $this->createRandomString(30);
$refresh_token = $this->createRandomString(30);
$tenantIdResponse = json_encode([
[
'id' => $this->createGuid(),
'tenantId' => $this->createGuid(),
'tenantType' => 'ORGANISATION',
],
]);
$tenantIdResponse = json_encode($expected);
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], $tenantIdResponse),
]);
$options = ['handler' => new HandlerStack($mock)];
$options = ['handler' => HandlerStack::create($mock)];

// Mocks the OAuth2 Client request factory and requests.
$refreshTokenResponse = json_encode([
Expand All @@ -88,7 +88,7 @@ public function testCreateFromRefreshToken(): void
$providerMock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], $refreshTokenResponse),
]);
$providerOptions = ['handler' => new HandlerStack($providerMock)];
$providerOptions = ['handler' => HandlerStack::create($providerMock)];

$httpClient = new Client($providerOptions);

Expand All @@ -103,29 +103,18 @@ public function testCreateFromRefreshToken(): void
'https://example.com/authorize'
);

$this->assertInstanceOf('\Radcliffe\Xero\XeroClient', $client);
$this->assertEquals($expected, $client->getTenantIds());
}

/**
* Tests creating from an access token.
*
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
*/
public function testCreateFromAccessToken(): void
{
$token = $this->createRandomString(30);
$tenantIdResponse = json_encode([
[
'id' => $this->createGuid(),
'tenantId' => $this->createGuid(),
'tenantType' => 'ORGANISATION',
],
]);
$mock = new MockHandler([
new Response(200, ['Content-Type' => 'application/json'], $tenantIdResponse),
]);
$options = ['handler' => new HandlerStack($mock)];
$options = [];

$client = XeroClient::createFromToken(
$this->clientId,
Expand Down
29 changes: 21 additions & 8 deletions tests/src/XeroClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,14 @@ public function testPublicApplication(): void
* @param array<string,string> $headers
* @param string $body
*
* @dataProvider providerGetTest
* @throws \GuzzleHttp\Exception\GuzzleException
*
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException|\GuzzleHttp\Exception\GuzzleException
* @dataProvider providerGetTest
*/
public function testGet(int $statusCode, array $headers, string $body): void
{
$options = $this->createConfiguration();

$mock = new MockHandler(
[
new Response($statusCode, $headers, $body)
Expand All @@ -48,6 +49,7 @@ public function testGet(int $statusCode, array $headers, string $body): void

$client = XeroClient::createFromConfig($options, [
'auth_token' => self::createRandomString(),
'tenant' => '46bda23d-0659-47d6-bcaf-d1419aca0e7f',
]);

$response = $client->request('GET', 'BrandingThemes');
Expand All @@ -65,7 +67,6 @@ public function testGet(int $statusCode, array $headers, string $body): void
* The expected number of connections.
*
* @dataProvider connectionsResponseProvider
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
*/
public function testGetConnections(int $statusCode, array $response, int $expectedCount): void
{
Expand Down Expand Up @@ -111,14 +112,26 @@ public function testWithoutAuthToken(): void
*/
public static function providerGetTest(): array
{
$json = [
'Id' => '3cc4210d-bf86-4cf5-aa5c-4a7c308dbfe1"',
'Status' => 'OK',
'ProviderName' => 'mradcliffe/xeroclient',
'DateTimeUTC' => '\/Date(1718810712561)',
'BrandingThemes' => [
[
'BrandingThemeID' => self::createGuid(),
'Name' => 'Standard',
'SortOrder' => 0,
'CreatedDateUTC' => '2010-06-29T18:16:36.27',
],
]
];

return [
[
200,
['Content-Type' => 'text/xml'],
'<?xml encoding="UTF-8" version="1.0"?><BrandingThemes><BrandingTheme><BrandingThemeID>' .
self::createGuid() .
'</BrandingThemeID><Name>Standard</Name><SortOrder>0</SortOrder><CreatedDateUTC>' .
'2010-06-29T18:16:36.27</CreatedDateUTC></BrandingTheme></BrandingThemes>',
['Content-Type' => 'application/json'],
json_encode($json),
]
];
}
Expand Down

0 comments on commit 6e48e75

Please sign in to comment.