Skip to content

Commit 2695fd9

Browse files
fix(client): return Detail on non-JSON 2xx for api_tokens and honeytoken-with-context
api_tokens() and create_honeytoken_with_context() guarded JSON parsing with a bare resp.ok, so a 2xx response with a non-JSON body (e.g. an HTML page served when the instance URL is wrong) crashed callers with a raw JSONDecodeError instead of returning a Detail. Gate JSON parsing on the response status and content type via the existing is_ok()/is_create_ok() helpers, matching every other endpoint, so a non-JSON body falls through to load_detail() and a Detail is returned. Fix the api_tokens success test to use the 200 the endpoint actually returns.
1 parent 75b79d9 commit 2695fd9

3 files changed

Lines changed: 54 additions & 3 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
### Fixed
2+
3+
- `api_tokens()` and `create_honeytoken_with_context()` now return a `Detail` instead of raising a raw `JSONDecodeError` when the server answers a `2xx` with a non-JSON body (e.g. an HTML page served when the instance URL is wrong). Like the other endpoints, they now gate JSON parsing on the response status and content type.

pygitguardian/client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ def api_tokens(
425425
result = Detail("The request timed out.")
426426
result.status_code = 504
427427
else:
428-
if resp.ok:
428+
if is_ok(resp):
429429
result = APITokensResponse.from_dict(resp.json())
430430
else:
431431
result = load_detail(resp)
@@ -741,7 +741,7 @@ def create_honeytoken_with_context(
741741
result = Detail("The request timed out.")
742742
result.status_code = 504
743743
else:
744-
if resp.ok:
744+
if is_create_ok(resp):
745745
result = HoneytokenWithContextResponse.from_dict(resp.json())
746746
else:
747747
result = load_detail(resp)

tests/test_client.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1002,7 +1002,7 @@ def test_api_tokens(client: GGClient, token):
10021002
mock_response = responses.get(
10031003
url=client._url_from_endpoint(f"api_tokens/{token}", "v1"),
10041004
content_type="application/json",
1005-
status=201,
1005+
status=200,
10061006
json={
10071007
"id": "5ddaad0c-5a0c-4674-beb5-1cd198d13360",
10081008
"name": "myTokenName",
@@ -1049,6 +1049,28 @@ def test_api_tokens_error(
10491049
assert isinstance(result, Detail)
10501050

10511051

1052+
@responses.activate
1053+
def test_api_tokens_non_json_body(client: GGClient):
1054+
"""
1055+
GIVEN an api_tokens endpoint answering 200 with a non-JSON body
1056+
(e.g. the dashboard SPA's HTML, served when the instance URL is wrong)
1057+
WHEN calling api_tokens
1058+
THEN it returns a Detail instead of raising a raw JSONDecodeError
1059+
"""
1060+
mock_response = responses.get(
1061+
url=client._url_from_endpoint("api_tokens/self", "v1"),
1062+
content_type="text/html",
1063+
status=200,
1064+
body="<!doctype html><html><body>GitGuardian</body></html>",
1065+
)
1066+
1067+
result = client.api_tokens()
1068+
1069+
assert mock_response.call_count == 1
1070+
assert isinstance(result, Detail)
1071+
assert result.status_code == 200
1072+
1073+
10521074
@responses.activate
10531075
def test_create_honeytoken(
10541076
client: GGClient,
@@ -1189,6 +1211,32 @@ def test_create_honeytoken_with_context_error(
11891211
assert isinstance(result, Detail)
11901212

11911213

1214+
@responses.activate
1215+
def test_create_honeytoken_with_context_non_json_body(client: GGClient):
1216+
"""
1217+
GIVEN the honeytoken with-context endpoint answering 2xx with a non-JSON body
1218+
WHEN calling create_honeytoken_with_context
1219+
THEN it returns a Detail instead of raising a raw JSONDecodeError
1220+
"""
1221+
mock_response = responses.post(
1222+
url=client._url_from_endpoint("honeytokens/with-context", "v1"),
1223+
content_type="text/html",
1224+
status=201,
1225+
body="<!doctype html><html></html>",
1226+
)
1227+
1228+
result = client.create_honeytoken_with_context(
1229+
name="honeytoken A",
1230+
description="honeytoken used in the repository AA",
1231+
type_="AWS",
1232+
filename="aws.yaml",
1233+
)
1234+
1235+
assert mock_response.call_count == 1
1236+
assert isinstance(result, Detail)
1237+
assert result.status_code == 201
1238+
1239+
11921240
@responses.activate
11931241
def test_create_jwt(
11941242
client: GGClient,

0 commit comments

Comments
 (0)