88from blueapi .config import OIDCConfig , OpaConfig , ServiceAccount
99from blueapi .service .authorization import (
1010 OpaClient ,
11+ OpaUserClient ,
1112 opa ,
13+ submit_permission ,
1214 validate_tiled_config ,
1315)
16+ from blueapi .service .model import TaskRequest
1417
1518# Reusable client patch decorator
1619patch_client_session = patch (
@@ -25,6 +28,7 @@ def opa_config() -> OpaConfig:
2528 return OpaConfig (
2629 root = HttpUrl ("http://auth.example.com" ),
2730 submit_task_check = "/auth/submit" ,
31+ admin_check = "/auth/admin" ,
2832 tiled_service_account_check = "/auth/tiled" ,
2933 )
3034
@@ -108,6 +112,105 @@ async def test_opa_adds_input_fields(session: MagicMock, opa_config: OpaConfig):
108112 )
109113
110114
115+ @pytest .mark .parametrize (
116+ "result,context" ,
117+ [(True , nullcontext ()), (False , pytest .raises (HTTPException , match = "403" ))],
118+ )
119+ @patch_client_session
120+ async def test_require_submit_task (
121+ session : MagicMock ,
122+ opa_config : OpaConfig ,
123+ result : bool ,
124+ context : AbstractContextManager ,
125+ ):
126+ session .return_value .post = AsyncMock (
127+ return_value = MagicMock (json = AsyncMock (return_value = {"result" : result }))
128+ )
129+
130+ client = OpaClient (instrument = "p99" , config = opa_config )
131+
132+ session .assert_called_once_with (base_url = "http://auth.example.com/" )
133+ with context :
134+ await client .require_submit_task (
135+ instrument_session = "cm12345-1" , token = "foo_bar"
136+ )
137+
138+ session ().post .assert_called_once_with (
139+ "/auth/submit" ,
140+ json = {
141+ "input" : {
142+ "token" : "foo_bar" ,
143+ "beamline" : "p99" ,
144+ "audience" : "account" ,
145+ "visit" : 1 ,
146+ "proposal" : 12345 ,
147+ }
148+ },
149+ )
150+
151+
152+ @patch_client_session
153+ async def test_opa_require_submit_task_invalid_session (
154+ session : MagicMock , opa_config : OpaConfig
155+ ):
156+ client = OpaClient (instrument = "p45" , config = opa_config )
157+
158+ with pytest .raises (ValueError ):
159+ await client .require_submit_task (
160+ instrument_session = "not a session" , token = "foo_bar"
161+ )
162+
163+
164+ @pytest .mark .parametrize ("result" , [True , False ])
165+ @patch_client_session
166+ async def test_opa_is_admin (session : MagicMock , opa_config : OpaConfig , result : bool ):
167+ session .return_value .post = AsyncMock (
168+ return_value = MagicMock (json = AsyncMock (return_value = {"result" : result }))
169+ )
170+ client = OpaClient (instrument = "p45" , config = opa_config )
171+
172+ admin = await client .is_admin ("foo_bar" )
173+
174+ assert admin == result
175+
176+ session ().post .assert_called_once_with (
177+ "/auth/admin" ,
178+ json = {"input" : {"token" : "foo_bar" , "beamline" : "p45" , "audience" : "account" }},
179+ )
180+
181+
182+ @pytest .mark .parametrize (
183+ "result,context" ,
184+ [
185+ (None , nullcontext ()),
186+ (HTTPException (status_code = 403 ), pytest .raises (HTTPException , match = "403" )),
187+ ],
188+ )
189+ async def test_user_client_can_submit_task (result , context : AbstractContextManager ):
190+ opa = MagicMock (spec = OpaUserClient )
191+ opa .require_submit_task = AsyncMock (side_effect = result )
192+
193+ user_client = OpaUserClient (opa , "foo_bar" )
194+
195+ with context :
196+ await user_client .can_submit_task (
197+ TaskRequest (name = "foo" , params = {}, instrument_session = "cm12345-1" )
198+ )
199+ opa .require_submit_task .assert_called_once_with ("cm12345-1" , "foo_bar" )
200+
201+
202+ @pytest .mark .parametrize ("result" , [True , False ])
203+ async def test_user_client_admin (result : bool ):
204+ opa = MagicMock (spec = OpaUserClient )
205+ opa .is_admin = AsyncMock (return_value = result )
206+
207+ user_client = OpaUserClient (opa , "foo_bar" )
208+
209+ admin = await user_client .admin ()
210+
211+ assert admin == result
212+
213+
111214async def test_validate_tiled_config ():
112215 opa = MagicMock (spec = OpaClient )
113216 tiled = ServiceAccount ()
@@ -177,3 +280,21 @@ async def test_opa_dependency_without_authz(token):
177280 del request .app .state .authz
178281 user_client = await opa (request , token )
179282 assert user_client is None
283+
284+
285+ @pytest .mark .parametrize (
286+ "result,context" ,
287+ [
288+ (None , nullcontext ()),
289+ (HTTPException (status_code = 403 ), pytest .raises (HTTPException , match = "403" )),
290+ ],
291+ )
292+ async def test_submit_permission_dependency (result , context : AbstractContextManager ):
293+ opa = MagicMock (spec = OpaUserClient )
294+ opa .can_submit_task .side_effect = result
295+ with context :
296+ await submit_permission (opa , Mock ())
297+
298+
299+ async def test_submit_permission_dependency_without_opa ():
300+ assert await submit_permission (None , Mock ()) is None
0 commit comments