1- from unittest .mock import MagicMock , patch
1+ from contextlib import AbstractContextManager , nullcontext
2+ from unittest .mock import AsyncMock , MagicMock , Mock , patch
23
34import pytest
45from pydantic import HttpUrl
56
6- from blueapi .config import OpaConfig
7+ from blueapi .config import OIDCConfig , OpaConfig , ServiceAccount
78from blueapi .service .authorization import (
89 OpaClient ,
10+ validate_tiled_config ,
911)
1012
1113# Reusable client patch decorator
2022def opa_config () -> OpaConfig :
2123 return OpaConfig (
2224 root = HttpUrl ("http://auth.example.com" ),
25+ tiled_service_account_check = "/auth/tiled" ,
2326 )
2427
2528
29+ @patch_client_session
30+ @pytest .mark .parametrize (
31+ "result,context" ,
32+ [
33+ (False , pytest .raises (ValueError , match = "Tiled service account is not valid " )),
34+ (True , nullcontext ()),
35+ ],
36+ )
37+ async def test_tiled_service_account (
38+ session : MagicMock ,
39+ opa_config : OpaConfig ,
40+ result : bool ,
41+ context : AbstractContextManager ,
42+ ):
43+ session .return_value .post = AsyncMock (
44+ return_value = MagicMock (json = AsyncMock (return_value = {"result" : result }))
45+ )
46+
47+ client = OpaClient (instrument = "p99" , config = opa_config )
48+
49+ session .assert_called_once_with (base_url = "http://auth.example.com/" )
50+ with context :
51+ await client .require_tiled_service_account (token = "foo_bar" )
52+ session ().post .assert_called_once_with (
53+ "/auth/tiled" ,
54+ json = {"input" : {"token" : "foo_bar" , "beamline" : "p99" , "audience" : "account" }},
55+ )
56+
57+
58+ @patch_client_session
59+ async def test_exception_raised_when_opa_fails (
60+ session : MagicMock , opa_config : OpaConfig
61+ ):
62+ session .return_value .post = AsyncMock (side_effect = RuntimeError ("Connection failed" ))
63+ async with OpaClient .for_config ("p45" , opa_config ) as client :
64+ assert client is not None
65+ with pytest .raises (RuntimeError , match = "Connection failed" ):
66+ await client .require_tiled_service_account (token = "foo_bar" )
67+
68+
2669@patch_client_session
2770async def test_session_closed (session : MagicMock , opa_config : OpaConfig ):
2871 async with OpaClient .for_config ("p45" , opa_config ):
@@ -46,3 +89,49 @@ async def test_opa_client_without_config(instrument: str | None):
4689async def test_opa_fails_without_instrument (opa_config : OpaConfig ):
4790 with pytest .raises (ValueError , match = "Instrument name is required" ):
4891 OpaClient .for_config (None , opa_config )
92+
93+
94+ async def test_validate_tiled_config ():
95+ opa = MagicMock (spec = OpaClient )
96+ tiled = ServiceAccount ()
97+ oidc = Mock (spec = OIDCConfig )
98+ oidc .token_endpoint = "token-endpoint"
99+ with patch ("blueapi.service.authorization.TiledAuth" ) as auth :
100+ auth .return_value .get_access_token .return_value = "tiled-token"
101+ await validate_tiled_config (tiled , oidc , opa )
102+
103+ auth .assert_called_once_with (tiled )
104+ opa .require_tiled_service_account .assert_called_once_with ("tiled-token" )
105+
106+
107+ @pytest .mark .parametrize (
108+ "tiled_auth,oidc,opa_client" ,
109+ [
110+ (None , None , MagicMock (spec = OpaClient )),
111+ (
112+ None ,
113+ OIDCConfig (well_known_url = "http://example.com" , client_id = "test-client" ),
114+ MagicMock (spec = OpaClient ),
115+ ),
116+ ("api_key" , None , MagicMock (spec = OpaClient )),
117+ (
118+ "api_key" ,
119+ OIDCConfig (well_known_url = "http://example.com" , client_id = "test-client" ),
120+ MagicMock (spec = OpaClient ),
121+ ),
122+ (ServiceAccount (), None , MagicMock (spec = OpaClient )),
123+ (
124+ ServiceAccount (),
125+ OIDCConfig (well_known_url = "http://example.com" , client_id = "test-client" ),
126+ None ,
127+ ),
128+ ],
129+ )
130+ async def test_validate_tiled_config_with_missing_config (
131+ tiled_auth : ServiceAccount | str | None ,
132+ oidc : OIDCConfig | None ,
133+ opa_client : MagicMock | None ,
134+ ):
135+ assert await validate_tiled_config (tiled_auth , oidc , opa_client ) is None
136+ if opa_client is not None :
137+ opa_client .require_tiled_service_account .assert_not_called ()
0 commit comments