Skip to content

Commit 524ca1a

Browse files
authored
Merge pull request #32 from stechstudio/copilot/fix-hubspot-api-key-issue
Replace dd() with HubSpotApiException for unhandled API status codes
2 parents 150889d + 84a38bc commit 524ca1a

File tree

3 files changed

+185
-1
lines changed

3 files changed

+185
-1
lines changed

src/Api/Client.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use Illuminate\Http\Client\Response;
88
use Illuminate\Support\Facades\Http;
99
use Illuminate\Support\Traits\ForwardsCalls;
10+
use STS\HubSpot\Exceptions\HubSpotApiException;
1011
use STS\HubSpot\Exceptions\InvalidRequestException;
1112
use STS\HubSpot\Exceptions\NotFoundException;
1213
use STS\HubSpot\Exceptions\RateLimitException;
@@ -43,7 +44,7 @@ public function http(): PendingRequest
4344
400 => throw new InvalidRequestException($response->json('message'), 400),
4445
404 => throw new NotFoundException($response, $exception),
4546
409 => throw new InvalidRequestException($response->json('message'), 409),
46-
default => dd($response->status(), $response->json())
47+
default => throw new HubSpotApiException($response, $exception)
4748
};
4849
});
4950
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
namespace STS\HubSpot\Exceptions;
4+
5+
use Illuminate\Http\Client\HttpClientException;
6+
use Illuminate\Http\Client\RequestException;
7+
use Illuminate\Http\Client\Response;
8+
9+
class HubSpotApiException extends HttpClientException
10+
{
11+
public Response $response;
12+
13+
public function __construct(Response $response, RequestException $previous)
14+
{
15+
parent::__construct(
16+
$response->json('message') ?? "HubSpot API returned status code {$response->status()}",
17+
$response->status(),
18+
$previous
19+
);
20+
21+
$this->response = $response;
22+
}
23+
}

tests/Unit/Api/ClientTest.php

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
<?php
2+
3+
use Illuminate\Support\Facades\Http;
4+
use STS\HubSpot\Api\Client;
5+
use STS\HubSpot\Exceptions\HubSpotApiException;
6+
use STS\HubSpot\Exceptions\InvalidRequestException;
7+
use STS\HubSpot\Exceptions\NotFoundException;
8+
use STS\HubSpot\Exceptions\RateLimitException;
9+
10+
beforeEach(function () {
11+
config()->set('hubspot.http.timeout', 10);
12+
config()->set('hubspot.http.connect_timeout', 10);
13+
});
14+
15+
test('throws InvalidRequestException for 400 status', function () {
16+
Http::fake([
17+
'*' => Http::response(['message' => 'Bad request'], 400),
18+
]);
19+
20+
$client = new Client('test-token');
21+
22+
expect(fn() => $client->get('/v3/objects/contacts'))
23+
->toThrow(InvalidRequestException::class);
24+
});
25+
26+
test('throws NotFoundException for 404 status', function () {
27+
Http::fake([
28+
'*' => Http::response(['message' => 'Not found'], 404),
29+
]);
30+
31+
$client = new Client('test-token');
32+
33+
expect(fn() => $client->get('/v3/objects/contacts/123'))
34+
->toThrow(NotFoundException::class);
35+
});
36+
37+
test('throws InvalidRequestException for 409 status', function () {
38+
Http::fake([
39+
'*' => Http::response(['message' => 'Conflict'], 409),
40+
]);
41+
42+
$client = new Client('test-token');
43+
44+
expect(fn() => $client->post('/v3/objects/contacts'))
45+
->toThrow(InvalidRequestException::class);
46+
});
47+
48+
test('throws RateLimitException for rate limit response', function () {
49+
Http::fake([
50+
'*' => Http::response(['category' => 'RATE_LIMITS', 'message' => 'Rate limit exceeded'], 429),
51+
]);
52+
53+
$client = new Client('test-token');
54+
55+
expect(fn() => $client->get('/v3/objects/contacts'))
56+
->toThrow(RateLimitException::class);
57+
});
58+
59+
test('throws HubSpotApiException for 401 unauthorized status', function () {
60+
Http::fake([
61+
'*' => Http::response(['message' => 'Unauthorized'], 401),
62+
]);
63+
64+
$client = new Client('test-token');
65+
66+
expect(fn() => $client->get('/v3/objects/contacts'))
67+
->toThrow(HubSpotApiException::class);
68+
});
69+
70+
test('throws HubSpotApiException for 403 forbidden status', function () {
71+
Http::fake([
72+
'*' => Http::response(['message' => 'Forbidden'], 403),
73+
]);
74+
75+
$client = new Client('test-token');
76+
77+
expect(fn() => $client->get('/v3/objects/contacts'))
78+
->toThrow(HubSpotApiException::class);
79+
});
80+
81+
test('throws HubSpotApiException for 422 unprocessable entity status', function () {
82+
Http::fake([
83+
'*' => Http::response(['message' => 'Unprocessable Entity'], 422),
84+
]);
85+
86+
$client = new Client('test-token');
87+
88+
expect(fn() => $client->post('/v3/objects/contacts'))
89+
->toThrow(HubSpotApiException::class);
90+
});
91+
92+
test('throws HubSpotApiException for 500 server error status', function () {
93+
Http::fake([
94+
'*' => Http::response(['message' => 'Internal Server Error'], 500),
95+
]);
96+
97+
$client = new Client('test-token');
98+
99+
expect(fn() => $client->get('/v3/objects/contacts'))
100+
->toThrow(HubSpotApiException::class);
101+
});
102+
103+
test('HubSpotApiException contains response object', function () {
104+
Http::fake([
105+
'*' => Http::response(['message' => 'Unauthorized', 'correlationId' => '123'], 401),
106+
]);
107+
108+
$client = new Client('test-token');
109+
110+
try {
111+
$client->get('/v3/objects/contacts');
112+
$this->fail('Expected HubSpotApiException to be thrown');
113+
} catch (HubSpotApiException $e) {
114+
expect($e->response)->not->toBeNull();
115+
expect($e->response->status())->toBe(401);
116+
expect($e->response->json('correlationId'))->toBe('123');
117+
}
118+
});
119+
120+
test('HubSpotApiException message includes status code when no message in response', function () {
121+
Http::fake([
122+
'*' => Http::response([], 401),
123+
]);
124+
125+
$client = new Client('test-token');
126+
127+
try {
128+
$client->get('/v3/objects/contacts');
129+
$this->fail('Expected HubSpotApiException to be thrown');
130+
} catch (HubSpotApiException $e) {
131+
expect($e->getMessage())->toContain('401');
132+
}
133+
});
134+
135+
test('HubSpotApiException uses response message when available', function () {
136+
Http::fake([
137+
'*' => Http::response(['message' => 'Missing required scopes'], 403),
138+
]);
139+
140+
$client = new Client('test-token');
141+
142+
try {
143+
$client->get('/v3/objects/contacts');
144+
$this->fail('Expected HubSpotApiException to be thrown');
145+
} catch (HubSpotApiException $e) {
146+
expect($e->getMessage())->toBe('Missing required scopes');
147+
}
148+
});
149+
150+
test('successful request does not throw exception', function () {
151+
Http::fake([
152+
'*' => Http::response(['results' => []], 200),
153+
]);
154+
155+
$client = new Client('test-token');
156+
$response = $client->get('/v3/objects/contacts');
157+
158+
expect($response->successful())->toBeTrue();
159+
expect($response->json('results'))->toBe([]);
160+
});

0 commit comments

Comments
 (0)