Skip to content

Commit 6e48e75

Browse files
committed
22: Resolves middleware not working after hours of troubleshooting guzzle's opaque error handling
1 parent c85e543 commit 6e48e75

File tree

3 files changed

+66
-45
lines changed

3 files changed

+66
-45
lines changed

src/XeroClient.php

+32-13
Original file line numberDiff line numberDiff line change
@@ -89,22 +89,24 @@ public function getConnections(): array
8989

9090
/**
9191
* {@inheritdoc}
92-
*
93-
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
9492
*/
9593
public static function createFromConfig(array $config, array $options = []): static
9694
{
97-
if (isset($options['tenant'])) {
98-
$config['headers']['xero-tenant-id'] = $options['tenant'];
95+
if (!isset($config['Accept'])) {
96+
$config['Accept'] = 'application/json';
97+
}
98+
99+
if (!isset($config['base_uri'])) {
100+
$config['base_uri'] = 'https://api.xero.com/api.xro/2.0/';
99101
}
100102

101103
if (isset($config['handler']) && is_a($config['handler'], '\GuzzleHttp\HandlerStack')) {
102-
$stack = $config['handler'];
104+
$stack = HandlerStack::create($config['handler']);
103105
} else {
104106
$stack = HandlerStack::create();
105107
}
106108

107-
$stack->push(Middleware::mapRequest(function (RequestInterface $request) use ($options) {
109+
$stack->before('prepare_body', Middleware::mapRequest(function (RequestInterface $request) use ($options) {
108110
$validUrls = array_filter(self::getValidUrls(), fn ($url) => str_starts_with($request->getUri(), $url));
109111
if (empty($validUrls)) {
110112
throw new XeroRequestException('API URL is not valid', $request);
@@ -113,8 +115,13 @@ public static function createFromConfig(array $config, array $options = []): sta
113115
if (!isset($options['auth_token'])) {
114116
throw new XeroRequestException('Missing required parameter auth_token', $request);
115117
}
116-
return $request->withHeader('Authorization', 'Bearer ' . $options['auth_token']);
117-
}));
118+
$new = $request->withHeader('Authorization', 'Bearer ' . $options['auth_token']);
119+
120+
if (isset($options['tenant'])) {
121+
$new = $new->withHeader('Xero-tenant-id', $options['tenant']);
122+
}
123+
return $new;
124+
}), 'xero');
118125

119126
$client = new Client($config + [
120127
'handler' => $stack,
@@ -126,7 +133,6 @@ public static function createFromConfig(array $config, array $options = []): sta
126133
* {@inheritdoc}
127134
*
128135
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
129-
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
130136
*/
131137
public static function createFromToken(
132138
string $id,
@@ -138,6 +144,8 @@ public static function createFromToken(
138144
array $collaborators = [],
139145
string $redirectUri = ''
140146
): static {
147+
$guzzle_options = [];
148+
$xero_options = [];
141149
if ($grant !== null) {
142150
// Fetch a new access token from a refresh token.
143151
$provider = new XeroProvider([
@@ -157,14 +165,20 @@ public static function createFromToken(
157165
$token = $refreshedToken->getToken();
158166
}
159167

168+
$guzzle_options['Accept'] = 'application/json';
169+
if (isset($options['handler'])) {
170+
$guzzle_options['handler'] = $options['handler'];
171+
}
160172
if (!isset($options['base_uri'])) {
161-
$options['base_uri'] = 'https://api.xero.com/api.xro/2.0/';
173+
$guzzle_options['base_uri'] = 'https://api.xero.com/api.xro/2.0/';
174+
}
175+
if (isset($options['tenant'])) {
176+
$xero_options['tenant'] = $options['tenant'];
162177
}
178+
$xero_options['auth_token'] = $token;
163179

164180
// Create a new static instance.
165-
$instance = self::createFromConfig($options, ['auth_token' => $token]);
166-
167-
$instance->tenantIds = $instance->getConnections();
181+
$instance = self::createFromConfig($guzzle_options, $xero_options);
168182

169183
if (isset($refreshedToken)) {
170184
$instance->refreshedToken = $refreshedToken;
@@ -226,10 +240,15 @@ public function getRefreshedToken(): ?AccessTokenInterface
226240
/**
227241
* The tenant guids accessible by this client.
228242
*
243+
* This will make a request if tenant ids is empty.
244+
*
229245
* @return string[]
230246
*/
231247
public function getTenantIds(): array
232248
{
249+
if (!$this->tenantIds) {
250+
$this->tenantIds = $this->getConnections();
251+
}
233252
return $this->tenantIds;
234253
}
235254
}

tests/src/XeroClientOAuth2Test.php

+13-24
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ public function testCreateFromTokenError(): void
4141
'instance' => $this->createGuid(),
4242
])),
4343
]);
44-
$options = ['handler' => new HandlerStack($mock)];
44+
$options = ['handler' => HandlerStack::create($mock)];
4545
$httpClient = new Client($options);
4646
$this->expectException(IdentityProviderException::class);
4747

@@ -60,23 +60,23 @@ public function testCreateFromTokenError(): void
6060
* Tests creating from a refresh token.
6161
*
6262
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
63-
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
6463
*/
6564
public function testCreateFromRefreshToken(): void
6665
{
66+
$expected = [
67+
[
68+
'id' => $this->createGuid(),
69+
'tenantId' => $this->createGuid(),
70+
'tenantType' => 'ORGANISATION',
71+
],
72+
];
6773
$token = $this->createRandomString(30);
6874
$refresh_token = $this->createRandomString(30);
69-
$tenantIdResponse = json_encode([
70-
[
71-
'id' => $this->createGuid(),
72-
'tenantId' => $this->createGuid(),
73-
'tenantType' => 'ORGANISATION',
74-
],
75-
]);
75+
$tenantIdResponse = json_encode($expected);
7676
$mock = new MockHandler([
7777
new Response(200, ['Content-Type' => 'application/json'], $tenantIdResponse),
7878
]);
79-
$options = ['handler' => new HandlerStack($mock)];
79+
$options = ['handler' => HandlerStack::create($mock)];
8080

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

9393
$httpClient = new Client($providerOptions);
9494

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

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

109109
/**
110110
* Tests creating from an access token.
111111
*
112112
* @throws \League\OAuth2\Client\Provider\Exception\IdentityProviderException
113-
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
114113
*/
115114
public function testCreateFromAccessToken(): void
116115
{
117116
$token = $this->createRandomString(30);
118-
$tenantIdResponse = json_encode([
119-
[
120-
'id' => $this->createGuid(),
121-
'tenantId' => $this->createGuid(),
122-
'tenantType' => 'ORGANISATION',
123-
],
124-
]);
125-
$mock = new MockHandler([
126-
new Response(200, ['Content-Type' => 'application/json'], $tenantIdResponse),
127-
]);
128-
$options = ['handler' => new HandlerStack($mock)];
117+
$options = [];
129118

130119
$client = XeroClient::createFromToken(
131120
$this->clientId,

tests/src/XeroClientTest.php

+21-8
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,14 @@ public function testPublicApplication(): void
3232
* @param array<string,string> $headers
3333
* @param string $body
3434
*
35-
* @dataProvider providerGetTest
35+
* @throws \GuzzleHttp\Exception\GuzzleException
3636
*
37-
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException|\GuzzleHttp\Exception\GuzzleException
37+
* @dataProvider providerGetTest
3838
*/
3939
public function testGet(int $statusCode, array $headers, string $body): void
4040
{
4141
$options = $this->createConfiguration();
42+
4243
$mock = new MockHandler(
4344
[
4445
new Response($statusCode, $headers, $body)
@@ -48,6 +49,7 @@ public function testGet(int $statusCode, array $headers, string $body): void
4849

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

5355
$response = $client->request('GET', 'BrandingThemes');
@@ -65,7 +67,6 @@ public function testGet(int $statusCode, array $headers, string $body): void
6567
* The expected number of connections.
6668
*
6769
* @dataProvider connectionsResponseProvider
68-
* @throws \Radcliffe\Xero\Exception\InvalidOptionsException
6970
*/
7071
public function testGetConnections(int $statusCode, array $response, int $expectedCount): void
7172
{
@@ -111,14 +112,26 @@ public function testWithoutAuthToken(): void
111112
*/
112113
public static function providerGetTest(): array
113114
{
115+
$json = [
116+
'Id' => '3cc4210d-bf86-4cf5-aa5c-4a7c308dbfe1"',
117+
'Status' => 'OK',
118+
'ProviderName' => 'mradcliffe/xeroclient',
119+
'DateTimeUTC' => '\/Date(1718810712561)',
120+
'BrandingThemes' => [
121+
[
122+
'BrandingThemeID' => self::createGuid(),
123+
'Name' => 'Standard',
124+
'SortOrder' => 0,
125+
'CreatedDateUTC' => '2010-06-29T18:16:36.27',
126+
],
127+
]
128+
];
129+
114130
return [
115131
[
116132
200,
117-
['Content-Type' => 'text/xml'],
118-
'<?xml encoding="UTF-8" version="1.0"?><BrandingThemes><BrandingTheme><BrandingThemeID>' .
119-
self::createGuid() .
120-
'</BrandingThemeID><Name>Standard</Name><SortOrder>0</SortOrder><CreatedDateUTC>' .
121-
'2010-06-29T18:16:36.27</CreatedDateUTC></BrandingTheme></BrandingThemes>',
133+
['Content-Type' => 'application/json'],
134+
json_encode($json),
122135
]
123136
];
124137
}

0 commit comments

Comments
 (0)