1212
1313from datetime import datetime , timedelta
1414from unittest import IsolatedAsyncioTestCase
15- from unittest .mock import patch
15+
16+ import unittest
17+ import pytest
18+ from unittest .mock import patch , MagicMock
19+ from openfga_sdk .credentials import CredentialConfiguration
20+ from openfga_sdk .oauth2 import OAuth2Client
21+
1622
1723import urllib3
1824
@@ -494,3 +500,127 @@ async def test_get_authentication_add_scheme_and_path(self, mock_request):
494500 },
495501 )
496502 await rest_client .close ()
503+
504+ class TestOAuth2ClientScopeHandling (unittest .TestCase ):
505+ """Test OAuth2Client scope serialization in synchronous context"""
506+
507+ def setUp (self ):
508+ self .config = CredentialConfiguration (
509+ client_id = "test_client_id" ,
510+ client_secret = "test_client_secret" ,
511+ api_issuer = "https://example.com" ,
512+ api_audience = "test_audience"
513+ )
514+ self .oauth_client = OAuth2Client (self .config )
515+
516+ @patch ('requests.post' )
517+ def test_list_scopes_serialization (self , mock_post ):
518+ """Test that list scopes are serialized as space-delimited string"""
519+ # Setup mock response
520+ mock_response = MagicMock ()
521+ mock_response .json .return_value = {"access_token" : "test_token" , "expires_in" : 3600 }
522+ mock_response .status_code = 200
523+ mock_post .return_value = mock_response
524+
525+ # Set list scopes
526+ self .config .scopes = ["read" , "write" , "admin" ]
527+
528+ # Make token request
529+ self .oauth_client .request_token ()
530+
531+ # Verify scope parameter was correctly formatted
532+ args , kwargs = mock_post .call_args
533+ self .assertIn ('data' , kwargs )
534+ self .assertIn ('scope' , kwargs ['data' ])
535+ self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
536+
537+ @patch ('requests.post' )
538+ def test_string_scope_serialization (self , mock_post ):
539+ """Test that string scope is used unchanged"""
540+ # Setup mock response
541+ mock_response = MagicMock ()
542+ mock_response .json .return_value = {"access_token" : "test_token" , "expires_in" : 3600 }
543+ mock_response .status_code = 200
544+ mock_post .return_value = mock_response
545+
546+ # Set string scope
547+ self .config .scopes = "read write admin"
548+
549+ # Make token request
550+ self .oauth_client .request_token ()
551+
552+ # Verify scope parameter was passed unchanged
553+ args , kwargs = mock_post .call_args
554+ self .assertIn ('data' , kwargs )
555+ self .assertIn ('scope' , kwargs ['data' ])
556+ self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
557+
558+
559+ class TestOAuth2ClientScopeHandlingAsync :
560+ """Test OAuth2Client scope serialization in asynchronous context"""
561+
562+ @pytest .fixture
563+ def oauth_client (self ):
564+ config = CredentialConfiguration (
565+ client_id = "test_client_id" ,
566+ client_secret = "test_client_secret" ,
567+ api_issuer = "https://example.com" ,
568+ api_audience = "test_audience"
569+ )
570+ return OAuth2Client (config )
571+
572+ @pytest .mark .asyncio
573+ async def test_list_scopes_serialization_async (self , oauth_client , monkeypatch ):
574+ """Test that list scopes are serialized as space-delimited string in async context"""
575+ # Mock the async HTTP response
576+ mock_response = MagicMock ()
577+ mock_response .json = MagicMock (return_value = {"access_token" : "test_token" , "expires_in" : 3600 })
578+ mock_response .status_code = 200
579+
580+ # Set up mock for the async HTTP client
581+ mock_client_session = MagicMock ()
582+ mock_client_session .post = MagicMock (return_value = mock_response )
583+ mock_client_session .__aenter__ = MagicMock (return_value = mock_client_session )
584+ mock_client_session .__aexit__ = MagicMock (return_value = None )
585+
586+ monkeypatch .setattr ('aiohttp.ClientSession' , MagicMock (return_value = mock_client_session ))
587+
588+ # Set list scopes
589+ oauth_client .configuration .scopes = ["read" , "write" , "admin" ]
590+
591+ # Make async token request
592+ await oauth_client .request_token_async ()
593+
594+ # Verify scope parameter was correctly formatted
595+ args , kwargs = mock_client_session .post .call_args
596+ self .assertIn ('data' , kwargs )
597+ self .assertIn ('scope' , kwargs ['data' ])
598+ self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
599+
600+ @pytest .mark .asyncio
601+ async def test_string_scope_serialization_async (self , oauth_client , monkeypatch ):
602+ """Test that string scope is used unchanged in async context"""
603+ # Mock the async HTTP response
604+ mock_response = MagicMock ()
605+ mock_response .json = MagicMock (return_value = {"access_token" : "test_token" , "expires_in" : 3600 })
606+ mock_response .status_code = 200
607+
608+ # Set up mock for the async HTTP client
609+ mock_client_session = MagicMock ()
610+ mock_client_session .post = MagicMock (return_value = mock_response )
611+ mock_client_session .__aenter__ = MagicMock (return_value = mock_client_session )
612+ mock_client_session .__aexit__ = MagicMock (return_value = None )
613+
614+ monkeypatch .setattr ('aiohttp.ClientSession' , MagicMock (return_value = mock_client_session ))
615+
616+ # Set string scope
617+ oauth_client .configuration .scopes = "read write admin"
618+
619+ # Make async token request
620+ await oauth_client .request_token_async ()
621+
622+ # Verify scope parameter was passed unchanged
623+ args , kwargs = mock_client_session .post .call_args
624+ self .assertIn ('data' , kwargs )
625+ self .assertIn ('scope' , kwargs ['data' ])
626+ self .assertEqual (kwargs ['data' ]['scope' ], "read write admin" )
0 commit comments