@@ -3285,33 +3285,105 @@ def test_rest_catalog_with_basic_auth_flat_properties(rest_mock: Mocker) -> None
32853285 assert rest_mock .last_request .headers ["Authorization" ] == expected_auth_header
32863286
32873287
3288- def test_rest_catalog_with_oauth2_auth_flat_properties (requests_mock : Mocker ) -> None :
3289- """OAuth2 auth configured via flattened env-var properties should work correctly.
32903288
3291- PYICEBERG_CATALOG__<NAME>__AUTH__OAUTH2__CLIENT_ID maps to 'auth.oauth2.client-id'.
3292- The dash is normalised to an underscore ('client_id') when forwarding to OAuth2AuthManager.
3293- """
3294- requests_mock .post (
3295- f"{ TEST_URI } oauth2/token" ,
3296- json = {
3297- "access_token" : "MTQ0NjJkZmQ5OTM2NDE1ZTZjNGZmZjI3" ,
3298- "token_type" : "Bearer" ,
3299- "expires_in" : 3600 ,
3300- },
3301- status_code = 200 ,
3302- )
3303- requests_mock .get (
3304- f"{ TEST_URI } v1/config" ,
3305- json = {"defaults" : {}, "overrides" : {}},
3306- status_code = 200 ,
3307- )
3308- catalog_properties = {
3309- "uri" : TEST_URI ,
3310- # Flat properties as produced by the env-var config parser (note: kebab-case keys)
3311- "auth.type" : "oauth2" ,
3312- "auth.oauth2.client-id" : "some_client_id" ,
3313- "auth.oauth2.client-secret" : "some_client_secret" ,
3314- "auth.oauth2.token-url" : f"{ TEST_URI } oauth2/token" ,
3315- }
3316- catalog = RestCatalog ("rest" , ** catalog_properties )
3317- assert catalog .uri == TEST_URI
3289+ import pytest
3290+
3291+ @pytest .mark .parametrize (
3292+ "catalog_properties, token_response, expected_scopes" ,
3293+ [
3294+ # OAuth2 with string scope
3295+ (
3296+ {
3297+ "uri" : TEST_URI ,
3298+ "auth.type" : "oauth2" ,
3299+ "auth.oauth2.client-id" : "some_client_id" ,
3300+ "auth.oauth2.client-secret" : "some_client_secret" ,
3301+ "auth.oauth2.token-url" : f"{ TEST_URI } oauth2/token" ,
3302+ "auth.oauth2.scope" : "read" ,
3303+ },
3304+ {"access_token" : "token" , "token_type" : "Bearer" , "expires_in" : 3600 , "scope" : "read" },
3305+ "read" ,
3306+ ),
3307+ # OAuth2 with list[str] scope
3308+ (
3309+ {
3310+ "uri" : TEST_URI ,
3311+ "auth.type" : "oauth2" ,
3312+ "auth.oauth2.client-id" : "id" ,
3313+ "auth.oauth2.client-secret" : "secret" ,
3314+ "auth.oauth2.token-url" : f"{ TEST_URI } oauth2/token" ,
3315+ "auth.oauth2.scope" : ["openid" , "profile" ],
3316+ },
3317+ {"access_token" : "token" , "token_type" : "Bearer" , "expires_in" : 3600 , "scope" : "openid profile" },
3318+ ["openid" , "profile" ],
3319+ ),
3320+ # Google with list[str] scopes
3321+ (
3322+ {
3323+ "uri" : TEST_URI ,
3324+ "auth.type" : "google" ,
3325+ "auth.google.credentials_path" : "/fake/path.json" ,
3326+ "auth.google.scopes" : ["scope1" , "scope2" ],
3327+ },
3328+ None ,
3329+ ["scope1" , "scope2" ],
3330+ ),
3331+ # Entra with list[str] scopes
3332+ (
3333+ {
3334+ "uri" : TEST_URI ,
3335+ "auth.type" : "entra" ,
3336+ "auth.entra.client-id" : "entra_id" ,
3337+ "auth.entra.client-secret" : "entra_secret" ,
3338+ "auth.entra.tenant-id" : "entra_tenant" ,
3339+ "auth.entra.scopes" : ["scopeA" , "scopeB" ],
3340+ },
3341+ None ,
3342+ ["scopeA" , "scopeB" ],
3343+ ),
3344+ ]
3345+ )
3346+ def test_rest_catalog_with_flat_properties_edge_cases (requests_mock , rest_mock , catalog_properties , token_response , expected_scopes ):
3347+ """Test flat property env-var style for OAuth2, Google, Entra with list[str] scopes and edge cases."""
3348+ if catalog_properties ["auth.type" ] == "oauth2" :
3349+ requests_mock .post (
3350+ f"{ TEST_URI } oauth2/token" ,
3351+ json = token_response ,
3352+ status_code = 200 ,
3353+ )
3354+ requests_mock .get (
3355+ f"{ TEST_URI } v1/config" ,
3356+ json = {"defaults" : {}, "overrides" : {}},
3357+ status_code = 200 ,
3358+ )
3359+ catalog = RestCatalog ("rest" , ** catalog_properties )
3360+ assert catalog .uri == TEST_URI
3361+ # If scope is a list, ensure it is handled as a space-separated string
3362+ if isinstance (expected_scopes , list ):
3363+ assert any (scope in token_response ["scope" ] for scope in expected_scopes )
3364+ else :
3365+ assert token_response ["scope" ] == expected_scopes
3366+ elif catalog_properties ["auth.type" ] == "google" :
3367+ # Patch google.auth.load_credentials_from_file and google.auth.transport.requests.Request
3368+ with mock .patch ("google.auth.load_credentials_from_file" ) as mock_load_creds , \
3369+ mock .patch ("google.auth.transport.requests.Request" ) as mock_google_request :
3370+ mock_credentials = mock .MagicMock ()
3371+ mock_credentials .token = "file_token"
3372+ mock_load_creds .return_value = (mock_credentials , "test_project_file" )
3373+ rest_mock .get (
3374+ f"{ TEST_URI } v1/config" ,
3375+ json = {"defaults" : {}, "overrides" : {}},
3376+ status_code = 200 ,
3377+ )
3378+ catalog = RestCatalog ("rest" , ** catalog_properties )
3379+ assert catalog .uri == TEST_URI
3380+ mock_load_creds .assert_called_with ("/fake/path.json" , scopes = expected_scopes )
3381+ elif catalog_properties ["auth.type" ] == "entra" :
3382+ # Just check that the catalog can be constructed and scopes are passed
3383+ rest_mock .get (
3384+ f"{ TEST_URI } v1/config" ,
3385+ json = {"defaults" : {}, "overrides" : {}},
3386+ status_code = 200 ,
3387+ )
3388+ catalog = RestCatalog ("rest" , ** catalog_properties )
3389+ assert catalog .uri == TEST_URI
0 commit comments