|
29 | 29 | import pytest |
30 | 30 | import respx |
31 | 31 |
|
| 32 | +from virl2_client.exceptions import APIError |
32 | 33 | from virl2_client.models import Lab |
33 | 34 | from virl2_client.virl2_client import ( |
34 | 35 | ClientLibrary, |
@@ -224,6 +225,97 @@ def initial_different_response( |
224 | 225 | assert respx.calls.call_count == 6 |
225 | 226 |
|
226 | 227 |
|
| 228 | +@respx.mock |
| 229 | +def test_jwt_only_valid_token_does_not_call_password_auth( |
| 230 | + client_library_server_current: MagicMock, |
| 231 | +): |
| 232 | + _ = client_library_server_current |
| 233 | + |
| 234 | + auth_route = respx.get(f"{FAKE_URL}api/v0/authentication").respond( |
| 235 | + 200, |
| 236 | + json={ |
| 237 | + "username": "jwt_user", |
| 238 | + "admin": False, |
| 239 | + "id": "6c7dd461-1cbe-428f-bdd5-545a0d766ed7", |
| 240 | + "token": "VALID_TOKEN", |
| 241 | + "error": None, |
| 242 | + }, |
| 243 | + ) |
| 244 | + password_auth_route = respx.post(f"{FAKE_URL}api/v0/authenticate").respond( |
| 245 | + json="SHOULD_NOT_BE_USED" |
| 246 | + ) |
| 247 | + respx.get(f"{FAKE_URL}api/v0/labs").respond(json=[]) |
| 248 | + |
| 249 | + cl = ClientLibrary(url=FAKE_URL, jwtoken="VALID_TOKEN") |
| 250 | + cl.all_labs() |
| 251 | + |
| 252 | + assert auth_route.called |
| 253 | + assert not password_auth_route.called |
| 254 | + |
| 255 | + |
| 256 | +@respx.mock |
| 257 | +def test_jwt_expired_with_credentials_reauths_using_password_auth( |
| 258 | + client_library_server_current: MagicMock, |
| 259 | +): |
| 260 | + _ = client_library_server_current |
| 261 | + |
| 262 | + auth_route = respx.get(f"{FAKE_URL}api/v0/authentication") |
| 263 | + auth_route.side_effect = [ |
| 264 | + httpx.Response(401), |
| 265 | + httpx.Response( |
| 266 | + 200, |
| 267 | + json={ |
| 268 | + "username": "test", |
| 269 | + "admin": True, |
| 270 | + "id": "6c7dd461-1cbe-428f-bdd5-545a0d766ed7", |
| 271 | + "token": "REFRESHED_TOKEN", |
| 272 | + "error": None, |
| 273 | + }, |
| 274 | + ), |
| 275 | + ] |
| 276 | + password_auth_route = respx.post(f"{FAKE_URL}api/v0/authenticate").respond( |
| 277 | + json="REFRESHED_TOKEN" |
| 278 | + ) |
| 279 | + |
| 280 | + ClientLibrary( |
| 281 | + url=FAKE_URL, |
| 282 | + username="test", |
| 283 | + password="pa$$", |
| 284 | + jwtoken="EXPIRED_TOKEN", |
| 285 | + ) |
| 286 | + |
| 287 | + assert auth_route.called |
| 288 | + assert auth_route.call_count == 2 |
| 289 | + assert password_auth_route.called |
| 290 | + assert json.loads(password_auth_route.calls[0].request.content) == { |
| 291 | + "username": "test", |
| 292 | + "password": "pa$$", |
| 293 | + } |
| 294 | + |
| 295 | + |
| 296 | +@respx.mock |
| 297 | +def test_jwt_reauth_without_credentials_fails_cleanly( |
| 298 | + client_library_server_current: MagicMock, |
| 299 | + reset_env: None, |
| 300 | +): |
| 301 | + _ = client_library_server_current, reset_env |
| 302 | + |
| 303 | + auth_route = respx.get(f"{FAKE_URL}api/v0/authentication").respond(401) |
| 304 | + password_auth_route = respx.post(f"{FAKE_URL}api/v0/authenticate").respond( |
| 305 | + json="SHOULD_NOT_BE_USED" |
| 306 | + ) |
| 307 | + |
| 308 | + with pytest.raises( |
| 309 | + APIError, |
| 310 | + match="JWT token expired and automatic re-authentication is not possible", |
| 311 | + ): |
| 312 | + ClientLibrary(url=FAKE_URL, jwtoken="EXPIRED_TOKEN") |
| 313 | + |
| 314 | + assert auth_route.called |
| 315 | + assert auth_route.call_count == 1 |
| 316 | + assert not password_auth_route.called |
| 317 | + |
| 318 | + |
227 | 319 | @respx.mock |
228 | 320 | def test_old_auth_url_used_with_cml_2_9( |
229 | 321 | client_library_server_2_9_0: MagicMock, monkeypatch: pytest.MonkeyPatch |
|
0 commit comments