Skip to content

Commit 32d2968

Browse files
committed
Integration tests
Signed-off-by: Saad Zaher <szaher@redhat.com>
1 parent 861823b commit 32d2968

15 files changed

Lines changed: 70 additions & 88 deletions

src/openshift_ai_auth/config.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import os
99
import warnings
1010
from dataclasses import dataclass, field
11-
from typing import List, Optional
11+
from typing import Optional
1212

1313
from .exceptions import ConfigurationError
1414

@@ -60,7 +60,7 @@ class AuthConfig:
6060
client_id: Optional[str] = None
6161
client_secret: Optional[str] = None
6262
openshift_token: Optional[str] = None
63-
scopes: List[str] = field(default_factory=lambda: ["openid"])
63+
scopes: list[str] = field(default_factory=lambda: ["openid"])
6464
use_device_flow: bool = False
6565
use_keyring: bool = False
6666
oidc_callback_port: int = 8080

src/openshift_ai_auth/strategies/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"""
88

99
from abc import ABC, abstractmethod
10-
from typing import Optional
1110

1211
from kubernetes.client import ApiClient
1312

src/openshift_ai_auth/strategies/incluster.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
import os
1111
from pathlib import Path
1212

13-
from kubernetes import client, config as k8s_config
13+
from kubernetes import client
14+
from kubernetes import config as k8s_config
1415
from kubernetes.client import ApiClient
1516
from kubernetes.config import ConfigException
1617

src/openshift_ai_auth/strategies/kubeconfig.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
from pathlib import Path
1212
from typing import Optional
1313

14-
from kubernetes import client, config as k8s_config
14+
from kubernetes import client
15+
from kubernetes import config as k8s_config
1516
from kubernetes.client import ApiClient
1617
from kubernetes.config import ConfigException
1718

@@ -114,7 +115,7 @@ def authenticate(self) -> ApiClient:
114115
) from e
115116
except Exception as e:
116117
raise AuthenticationError(
117-
f"Unexpected error loading kubeconfig",
118+
"Unexpected error loading kubeconfig",
118119
f"Error: {type(e).__name__}: {str(e)}"
119120
) from e
120121

src/openshift_ai_auth/strategies/oidc.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,16 @@
1212

1313
import base64
1414
import hashlib
15-
import json
1615
import logging
17-
import os
1816
import secrets
1917
import threading
2018
import time
2119
import webbrowser
2220
from http.server import BaseHTTPRequestHandler, HTTPServer
23-
from typing import Optional, Dict, Any, Tuple
24-
from urllib.parse import urlencode, urlparse, parse_qs
21+
from typing import Any, Optional
22+
from urllib.parse import parse_qs, urlencode, urlparse
2523

2624
import requests
27-
from kubernetes import client
2825
from kubernetes.client import ApiClient, Configuration
2926

3027
from ..config import AuthConfig
@@ -76,7 +73,7 @@ def __init__(self, config: AuthConfig) -> None:
7673
config: AuthConfig instance with OIDC parameters
7774
"""
7875
super().__init__(config)
79-
self._oidc_config: Optional[Dict[str, Any]] = None
76+
self._oidc_config: Optional[dict[str, Any]] = None
8077
self._access_token: Optional[str] = None
8178
self._refresh_token: Optional[str] = None
8279
self._token_expiry: Optional[float] = None
@@ -155,7 +152,7 @@ def authenticate(self) -> ApiClient:
155152
# Create and configure ApiClient
156153
return self._create_api_client()
157154

158-
def _discover_oidc_config(self) -> Dict[str, Any]:
155+
def _discover_oidc_config(self) -> dict[str, Any]:
159156
"""Discover OIDC configuration from issuer.
160157
161158
Fetches the .well-known/openid-configuration document.
@@ -210,7 +207,7 @@ def _authenticate_device_flow(self) -> None:
210207
if not device_authorization_endpoint:
211208
raise AuthenticationError(
212209
"Device Code Flow not supported by this OIDC provider",
213-
f"The OIDC discovery document does not include 'device_authorization_endpoint'"
210+
"The OIDC discovery document does not include 'device_authorization_endpoint'"
214211
)
215212

216213
# Request device code
@@ -246,10 +243,10 @@ def _authenticate_device_flow(self) -> None:
246243
interval = device_response.get("interval", 5)
247244

248245
print(f"\n{'='*60}")
249-
print(f"OIDC Device Code Authentication")
246+
print("OIDC Device Code Authentication")
250247
print(f"{'='*60}")
251248
if verification_uri_complete:
252-
print(f"\nPlease visit this URL to authenticate:")
249+
print("\nPlease visit this URL to authenticate:")
253250
print(f"\n {verification_uri_complete}\n")
254251
else:
255252
print(f"\nPlease visit this URL: {verification_uri}")
@@ -413,7 +410,7 @@ def log_message(self, format, *args):
413410
auth_url = f"{authorization_endpoint}?{urlencode(auth_params)}"
414411

415412
# Open browser
416-
print(f"\nOpening browser for authentication...")
413+
print("\nOpening browser for authentication...")
417414
print(f"If the browser doesn't open, visit this URL:\n{auth_url}\n")
418415

419416
webbrowser.open(auth_url)

src/openshift_ai_auth/strategies/openshift.py

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,22 @@
1414

1515
import base64
1616
import hashlib
17-
import json
1817
import logging
1918
import os
2019
import secrets
2120
import threading
22-
import time
2321
import webbrowser
2422
from http.server import BaseHTTPRequestHandler, HTTPServer
25-
from typing import Optional, Dict, Any, Tuple
26-
from urllib.parse import urlencode, urlparse, parse_qs
23+
from typing import Any, Optional
24+
from urllib.parse import parse_qs, urlencode, urlparse
2725

2826
import requests
29-
from kubernetes import client
3027
from kubernetes.client import ApiClient, Configuration
3128

3229
from ..config import AuthConfig
3330
from ..exceptions import (
3431
AuthenticationError,
3532
ConfigurationError,
36-
OpenShiftOAuthError,
3733
StrategyNotAvailableError,
3834
)
3935
from .base import AuthStrategy
@@ -81,7 +77,7 @@ def __init__(self, config: AuthConfig) -> None:
8177
config: AuthConfig instance with OpenShift parameters
8278
"""
8379
super().__init__(config)
84-
self._oauth_metadata: Optional[Dict[str, Any]] = None
80+
self._oauth_metadata: Optional[dict[str, Any]] = None
8581
self._access_token: Optional[str] = None
8682

8783
def is_available(self) -> bool:
@@ -164,7 +160,7 @@ def authenticate(self) -> ApiClient:
164160
# Create and configure ApiClient
165161
return self._create_api_client()
166162

167-
def _discover_oauth_metadata(self) -> Dict[str, Any]:
163+
def _discover_oauth_metadata(self) -> dict[str, Any]:
168164
"""Discover OpenShift OAuth server metadata.
169165
170166
Fetches the /.well-known/oauth-authorization-server document.
@@ -297,7 +293,7 @@ def log_message(self, format, *args):
297293
auth_url = f"{authorization_endpoint}?{urlencode(auth_params)}"
298294

299295
# Open browser
300-
print(f"\nOpening browser for OpenShift authentication...")
296+
print("\nOpening browser for OpenShift authentication...")
301297
print(f"If the browser doesn't open, visit this URL:\n{auth_url}\n")
302298

303299
webbrowser.open(auth_url)

tests/conftest.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,8 @@
55
environments, kubeconfig files, and service account tokens.
66
"""
77

8-
import os
8+
from collections.abc import Generator
99
from pathlib import Path
10-
from typing import Generator
1110

1211
import pytest
1312

tests/integration/test_oidc_integration.py

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,15 @@
1010
pytest tests/integration/ -v # Run all integration tests
1111
"""
1212

13-
import pytest
1413
import warnings
15-
from unittest.mock import patch, MagicMock
14+
from unittest.mock import MagicMock, patch
15+
16+
import pytest
1617

1718
from openshift_ai_auth import AuthConfig
1819
from openshift_ai_auth.config import SecurityWarning
19-
from openshift_ai_auth.strategies.oidc import OIDCStrategy
2020
from openshift_ai_auth.exceptions import AuthenticationError
21+
from openshift_ai_auth.strategies.oidc import OIDCStrategy
2122

2223

2324
@pytest.mark.integration
@@ -45,11 +46,11 @@ def test_full_auth_code_flow_with_mock_server(self, mock_oauth_server, mock_env_
4546
assert strategy.is_available()
4647

4748
# Mock the browser opening and callback handling
48-
with patch('webbrowser.open') as mock_browser:
49+
with patch('webbrowser.open'):
4950
# Mock the callback server to simulate successful auth
5051
with patch('openshift_ai_auth.strategies.oidc.HTTPServer') as mock_server:
5152
# Simulate auth code callback
52-
mock_handler_instance = MagicMock()
53+
MagicMock()
5354
mock_server_instance = MagicMock()
5455
mock_server.return_value = mock_server_instance
5556

@@ -59,17 +60,7 @@ def test_full_auth_code_flow_with_mock_server(self, mock_oauth_server, mock_env_
5960
def simulate_callback(*args, **kwargs):
6061
"""Simulate receiving auth code."""
6162
# Request auth code from mock server
62-
import requests
63-
from urllib.parse import urlencode
64-
65-
params = {
66-
"client_id": mock_oauth_server.client_id,
67-
"response_type": "code",
68-
"redirect_uri": "http://localhost:8080/callback",
69-
"code_challenge": "test_challenge",
70-
"code_challenge_method": "S256",
71-
"state": "test_state",
72-
}
63+
7364

7465
# The mock server will auto-approve and return a code
7566
# We simulate this by directly calling token endpoint
@@ -79,7 +70,6 @@ def simulate_callback(*args, **kwargs):
7970

8071
# For this test, we'll patch the entire auth flow
8172
# to use our mock server's tokens
82-
original_auth = strategy._authenticate_auth_code_flow
8373

8474
def mock_auth():
8575
"""Mock authentication that uses real mock server."""
@@ -241,11 +231,12 @@ class TestOIDCIntegrationPKCE:
241231

242232
def test_pkce_code_challenge_generation(self, mock_oauth_server, mock_env_vars):
243233
"""Test that PKCE code challenge is properly generated and verified."""
244-
import requests
245234
import base64
246235
import hashlib
247236
import secrets
248237

238+
import requests
239+
249240
# Generate PKCE parameters
250241
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
251242
code_challenge = base64.urlsafe_b64encode(
@@ -272,7 +263,7 @@ def test_pkce_code_challenge_generation(self, mock_oauth_server, mock_env_vars):
272263
assert "code=" in location
273264

274265
# Extract auth code
275-
from urllib.parse import urlparse, parse_qs
266+
from urllib.parse import parse_qs, urlparse
276267
parsed = urlparse(location)
277268
params = parse_qs(parsed.query)
278269
auth_code = params["code"][0]
@@ -297,11 +288,12 @@ def test_pkce_code_challenge_generation(self, mock_oauth_server, mock_env_vars):
297288

298289
def test_pkce_verification_failure(self, mock_oauth_server, mock_env_vars):
299290
"""Test that PKCE verification fails with wrong verifier."""
300-
import requests
301291
import base64
302292
import hashlib
303293
import secrets
304294

295+
import requests
296+
305297
# Generate PKCE parameters
306298
code_verifier = base64.urlsafe_b64encode(secrets.token_bytes(32)).decode('utf-8').rstrip('=')
307299
code_challenge = base64.urlsafe_b64encode(
@@ -322,7 +314,7 @@ def test_pkce_verification_failure(self, mock_oauth_server, mock_env_vars):
322314
)
323315

324316
# Extract auth code
325-
from urllib.parse import urlparse, parse_qs
317+
from urllib.parse import parse_qs, urlparse
326318
location = auth_response.headers.get("Location", "")
327319
parsed = urlparse(location)
328320
params = parse_qs(parsed.query)

tests/mock_oauth_server.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717
import threading
1818
import time
1919
from http.server import BaseHTTPRequestHandler, HTTPServer
20-
from typing import Dict, Any, Optional
21-
from urllib.parse import parse_qs, urlparse, urlencode
20+
from typing import Any, Optional
21+
from urllib.parse import parse_qs, urlencode, urlparse
2222

2323

2424
class MockOAuthServer:
@@ -38,9 +38,9 @@ def __init__(self, host: str = "localhost", port: int = 9999):
3838
self.base_url = f"http://{host}:{port}"
3939

4040
# Storage for active flows
41-
self.auth_codes: Dict[str, Dict[str, Any]] = {}
42-
self.device_codes: Dict[str, Dict[str, Any]] = {}
43-
self.tokens: Dict[str, Dict[str, Any]] = {}
41+
self.auth_codes: dict[str, dict[str, Any]] = {}
42+
self.device_codes: dict[str, dict[str, Any]] = {}
43+
self.tokens: dict[str, dict[str, Any]] = {}
4444

4545
# Configuration
4646
self.issuer = self.base_url
@@ -135,7 +135,7 @@ def _handle_openshift_discovery(self):
135135
}
136136
self._send_json(200, discovery)
137137

138-
def _handle_authorize(self, params: Dict[str, list]):
138+
def _handle_authorize(self, params: dict[str, list]):
139139
"""Handle authorization request."""
140140
client_id = params.get("client_id", [""])[0]
141141
redirect_uri = params.get("redirect_uri", [""])[0]
@@ -171,7 +171,7 @@ def _handle_authorize(self, params: Dict[str, list]):
171171
# Return approval page (for manual testing)
172172
self._send_html(200, "<h1>Approval Page</h1><p>Auto-approve is disabled</p>")
173173

174-
def _handle_token(self, params: Dict[str, list]):
174+
def _handle_token(self, params: dict[str, list]):
175175
"""Handle token endpoint requests."""
176176
grant_type = params.get("grant_type", [""])[0]
177177
client_id = params.get("client_id", [""])[0]
@@ -188,10 +188,10 @@ def _handle_token(self, params: Dict[str, list]):
188188
"error_description": f"Grant type '{grant_type}' is not supported"
189189
})
190190

191-
def _handle_token_auth_code(self, params: Dict[str, list], client_id: str):
191+
def _handle_token_auth_code(self, params: dict[str, list], client_id: str):
192192
"""Handle authorization code grant."""
193193
code = params.get("code", [""])[0]
194-
redirect_uri = params.get("redirect_uri", [""])[0]
194+
params.get("redirect_uri", [""])[0]
195195
code_verifier = params.get("code_verifier", [""])[0]
196196

197197
# Validate auth code
@@ -259,7 +259,7 @@ def _handle_token_auth_code(self, params: Dict[str, list], client_id: str):
259259
"scope": "openid profile email"
260260
})
261261

262-
def _handle_token_refresh(self, params: Dict[str, list], client_id: str):
262+
def _handle_token_refresh(self, params: dict[str, list], client_id: str):
263263
"""Handle refresh token grant."""
264264
refresh_token = params.get("refresh_token", [""])[0]
265265

@@ -285,7 +285,7 @@ def _handle_token_refresh(self, params: Dict[str, list], client_id: str):
285285
"scope": "openid profile email"
286286
})
287287

288-
def _handle_device_code(self, params: Dict[str, list]):
288+
def _handle_device_code(self, params: dict[str, list]):
289289
"""Handle device code request."""
290290
client_id = params.get("client_id", [""])[0]
291291

@@ -308,7 +308,7 @@ def _handle_device_code(self, params: Dict[str, list]):
308308
"interval": 1 # Fast polling for tests
309309
})
310310

311-
def _handle_token_device_code(self, params: Dict[str, list], client_id: str):
311+
def _handle_token_device_code(self, params: dict[str, list], client_id: str):
312312
"""Handle device code token request (polling)."""
313313
device_code = params.get("device_code", [""])[0]
314314

0 commit comments

Comments
 (0)