Skip to content

Commit a777ccc

Browse files
test(spp_api_v2): add coverage for OAuth token endpoint formats (#73)
Add tests for untested code paths in _parse_token_request: - Basic Auth only (no body/Content-Type) - Form body overrides Basic Auth header credentials - Basic Auth supplements missing form body credentials - Malformed base64 in Authorization header - Basic Auth with no colon separator - No credentials at all returns 400
1 parent b99d82c commit a777ccc

1 file changed

Lines changed: 123 additions & 0 deletions

File tree

spp_api_v2/tests/test_oauth.py

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,129 @@ def test_token_generation_form_encoded(self):
269269
self.assertIn("individual:read", data["scope"])
270270
self.assertIn("group:search", data["scope"])
271271

272+
def test_basic_auth_only_no_body(self):
273+
"""Basic Auth header with no body or Content-Type returns access token"""
274+
credentials = base64.b64encode(f"{self.client.client_id}:{self.client.client_secret}".encode()).decode("utf-8")
275+
276+
response = self.url_open(
277+
self.url,
278+
data="{}",
279+
headers={"Authorization": f"Basic {credentials}"},
280+
)
281+
282+
self.assertEqual(response.status_code, 200)
283+
284+
data = json.loads(response.content)
285+
self.assertIn("access_token", data)
286+
self.assertEqual(data["token_type"], "Bearer")
287+
288+
def test_basic_auth_header_with_form_body_override(self):
289+
"""Form body credentials take precedence over Basic Auth header"""
290+
# Basic Auth header has WRONG secret
291+
wrong_credentials = base64.b64encode(f"{self.client.client_id}:wrong-secret".encode()).decode("utf-8")
292+
293+
# Form body has CORRECT credentials
294+
body = urlencode(
295+
{
296+
"grant_type": "client_credentials",
297+
"client_id": self.client.client_id,
298+
"client_secret": self.client.client_secret,
299+
}
300+
)
301+
302+
response = self.url_open(
303+
self.url,
304+
data=body,
305+
headers={
306+
"Content-Type": "application/x-www-form-urlencoded",
307+
"Authorization": f"Basic {wrong_credentials}",
308+
},
309+
)
310+
311+
# Should succeed because form body credentials take precedence
312+
self.assertEqual(response.status_code, 200)
313+
314+
def test_basic_auth_supplements_form_body(self):
315+
"""Basic Auth fills in missing form body credentials"""
316+
credentials = base64.b64encode(f"{self.client.client_id}:{self.client.client_secret}".encode()).decode("utf-8")
317+
318+
# Form body has grant_type only, no client_id/client_secret
319+
body = urlencode({"grant_type": "client_credentials"})
320+
321+
response = self.url_open(
322+
self.url,
323+
data=body,
324+
headers={
325+
"Content-Type": "application/x-www-form-urlencoded",
326+
"Authorization": f"Basic {credentials}",
327+
},
328+
)
329+
330+
self.assertEqual(response.status_code, 200)
331+
332+
data = json.loads(response.content)
333+
self.assertIn("access_token", data)
334+
335+
def test_malformed_base64_auth_header(self):
336+
"""Malformed base64 in Authorization header is ignored gracefully"""
337+
body = urlencode(
338+
{
339+
"grant_type": "client_credentials",
340+
"client_id": self.client.client_id,
341+
"client_secret": self.client.client_secret,
342+
}
343+
)
344+
345+
response = self.url_open(
346+
self.url,
347+
data=body,
348+
headers={
349+
"Content-Type": "application/x-www-form-urlencoded",
350+
"Authorization": "Basic !!!not-valid-base64!!!",
351+
},
352+
)
353+
354+
# Should still succeed via form body credentials
355+
self.assertEqual(response.status_code, 200)
356+
357+
def test_basic_auth_no_colon_in_decoded(self):
358+
"""Basic Auth with no colon separator is ignored"""
359+
# Encode a value without colon
360+
no_colon = base64.b64encode(b"no-colon-here").decode("utf-8")
361+
362+
body = urlencode(
363+
{
364+
"grant_type": "client_credentials",
365+
"client_id": self.client.client_id,
366+
"client_secret": self.client.client_secret,
367+
}
368+
)
369+
370+
response = self.url_open(
371+
self.url,
372+
data=body,
373+
headers={
374+
"Content-Type": "application/x-www-form-urlencoded",
375+
"Authorization": f"Basic {no_colon}",
376+
},
377+
)
378+
379+
# Should still succeed via form body credentials
380+
self.assertEqual(response.status_code, 200)
381+
382+
def test_no_credentials_returns_400(self):
383+
"""No credentials at all returns 400"""
384+
response = self.url_open(
385+
self.url,
386+
data="not-json-not-form",
387+
headers={"Content-Type": "text/plain"},
388+
)
389+
390+
self.assertEqual(response.status_code, 400)
391+
data = json.loads(response.content)
392+
self.assertIn("detail", data)
393+
self.assertIn("Unable to parse", data["detail"])
394+
272395
def test_token_no_scopes(self):
273396
"""Client with no scopes still gets token but empty scope string"""
274397
# Create client without scopes

0 commit comments

Comments
 (0)