55that would be used in production environments.
66"""
77
8- from unittest import TestCase
8+ from django . test import TestCase
99
1010import casbin
11- from ddt import data as test_data
11+ from ddt import data as ddt_data
1212from ddt import ddt , unpack
1313
1414from openedx_authz .engine .enforcer import enforcer as global_enforcer
1919class PolicyLoadingTestSetupMixin (TestCase ):
2020 """Mixin providing policy loading test utilities."""
2121
22+ @staticmethod
23+ def _count_policies_in_file (scope_pattern : str = None , role : str = None ):
24+ """Count policies in the authz.policy file matching the given criteria.
25+
26+ This provides a dynamic way to get expected policy counts without
27+ hardcoding values that might change as the policy file evolves.
28+
29+ Args:
30+ scope_pattern: Scope pattern to match (e.g., 'lib:*')
31+ role: Role to match (e.g., 'role:library_admin')
32+
33+ Returns:
34+ int: Number of matching policies
35+ """
36+ count = 0
37+ with open ("openedx_authz/engine/config/authz.policy" , "r" ) as f :
38+ for line in f :
39+ line = line .strip ()
40+ if not line or line .startswith ("#" ):
41+ continue
42+ if not line .startswith ("p," ):
43+ continue
44+
45+ parts = [p .strip () for p in line .split ("," )]
46+ if len (parts ) < 4 :
47+ continue
48+
49+ # parts[0] = 'p', parts[1] = role, parts[2] = action, parts[3] = scope
50+ matches = True
51+ if role and parts [1 ] != role :
52+ matches = False
53+ if scope_pattern and parts [3 ] != scope_pattern :
54+ matches = False
55+
56+ if matches :
57+ count += 1
58+ return count
59+
2260 def _seed_database_with_policies (self ):
2361 """Seed the database with policies from the policy file.
2462
@@ -120,9 +158,16 @@ class TestPolicyLoadingStrategies(PolicyLoadingTestSetupMixin):
120158
121159 These tests demonstrate how policy loading would work in real-world scenarios,
122160 including scope-based loading, user-context loading, and role-specific loading.
123- This provides examples for how the application should load policies in production .
161+ All based on our basic policy setup in authz.policy file .
124162 """
125163
164+ LIBRARY_ROLES = [
165+ "role:library_user" ,
166+ "role:library_admin" ,
167+ "role:library_author" ,
168+ "role:library_collaborator" ,
169+ ]
170+
126171 def setUp (self ):
127172 """Set up test environment without auto-loading policies."""
128173 super ().setUp ()
@@ -133,13 +178,12 @@ def tearDown(self):
133178 global_enforcer .clear_policy ()
134179 super ().tearDown ()
135180
136- @test_data (
137- ( "lib:*" , 4 ), # Library policies from authz.policy file
138- ( "course:*" , 0 ), # No course policies in basic setup
139- ( "org:*" , 0 ), # No org policies in basic setup
181+ @ddt_data (
182+ "lib:*" , # Library policies from authz.policy file
183+ "course:*" , # No course policies in basic setup
184+ "org:*" , # No org policies in basic setup
140185 )
141- @unpack
142- def test_scope_based_policy_loading (self , scope , expected_policy_count ):
186+ def test_scope_based_policy_loading (self , scope ):
143187 """Test loading policies for specific scopes.
144188
145189 This demonstrates how an application would load only policies
@@ -150,21 +194,26 @@ def test_scope_based_policy_loading(self, scope, expected_policy_count):
150194 - Only scope-relevant policies are loaded
151195 - Policy count matches expected for scope
152196 """
197+ expected_policy_count = self ._count_policies_in_file (scope_pattern = scope )
153198 initial_policy_count = len (global_enforcer .get_policy ())
154199
155200 self ._load_policies_for_scope (scope )
201+ loaded_policies = global_enforcer .get_policy ()
156202
157203 self .assertEqual (initial_policy_count , 0 )
158- loaded_policies = global_enforcer .get_policy ()
159204 self .assertEqual (len (loaded_policies ), expected_policy_count )
160205
161- # Verify that only policies for the requested scope are loaded
162206 if expected_policy_count > 0 :
163207 scope_prefix = scope .replace ("*" , "" )
164208 for policy in loaded_policies :
165209 self .assertTrue (policy [2 ].startswith (scope_prefix ))
166210
167- def test_user_context_policy_loading (self ):
211+ @ddt_data (
212+ ("user:alice" , ["lib:*" ]),
213+ ("user:bob" , ["lib:*" ]),
214+ )
215+ @unpack
216+ def test_user_context_policy_loading (self , user , user_scopes ):
168217 """Test loading policies based on user context.
169218
170219 This demonstrates loading policies when a user logs in or
@@ -175,17 +224,16 @@ def test_user_context_policy_loading(self):
175224 - Policies are loaded for user's scopes
176225 - Policy count is reasonable for context
177226 """
178- user = "user:alice"
179- user_scopes = ["lib:math_101" , "lib:science_301" ]
180227 initial_policy_count = len (global_enforcer .get_policy ())
181228
182229 self ._load_policies_for_user_context (user , user_scopes )
230+ loaded_policies = global_enforcer .get_policy ()
183231
184232 self .assertEqual (initial_policy_count , 0 )
185- loaded_policies = global_enforcer .get_policy ()
186233 self .assertGreater (len (loaded_policies ), 0 )
187234
188- def test_role_specific_policy_loading (self ):
235+ @ddt_data (* LIBRARY_ROLES )
236+ def test_role_specific_policy_loading (self , role_name ):
189237 """Test loading policies for specific role management operations.
190238
191239 This demonstrates loading policies when performing administrative
@@ -196,13 +244,12 @@ def test_role_specific_policy_loading(self):
196244 - Role-specific policies are loaded
197245 - Loaded policies contain expected role
198246 """
199- role_name = "role:library_admin"
200247 initial_policy_count = len (global_enforcer .get_policy ())
201248
202249 self ._load_policies_for_role_management (role_name )
250+ loaded_policies = global_enforcer .get_policy ()
203251
204252 self .assertEqual (initial_policy_count , 0 )
205- loaded_policies = global_enforcer .get_policy ()
206253 self .assertGreater (len (loaded_policies ), 0 )
207254
208255 role_found = any (role_name in str (policy ) for policy in loaded_policies )
@@ -220,19 +267,23 @@ def test_policy_loading_lifecycle(self):
220267 - No policies exist at startup
221268 """
222269 startup_policy_count = len (global_enforcer .get_policy ())
270+
223271 self .assertEqual (startup_policy_count , 0 )
224272
225273 self ._load_policies_for_scope ("lib:*" )
226274 library_policy_count = len (global_enforcer .get_policy ())
275+
227276 self .assertGreater (library_policy_count , 0 )
228277
229278 self ._load_policies_for_role_management ("role:library_admin" )
230279 admin_policy_count = len (global_enforcer .get_policy ())
280+
231281 self .assertLessEqual (admin_policy_count , library_policy_count )
232282
233- self ._load_policies_for_user_context ("user:alice" , ["lib:math_101 " ])
283+ self ._load_policies_for_user_context ("user:alice" , ["lib:* " ])
234284 user_policy_count = len (global_enforcer .get_policy ())
235- self .assertGreaterEqual (user_policy_count , 0 )
285+
286+ self .assertEqual (user_policy_count , library_policy_count )
236287
237288 def test_empty_enforcer_behavior (self ):
238289 """Test behavior when no policies are loaded.
@@ -246,15 +297,14 @@ def test_empty_enforcer_behavior(self):
246297 - No enforcement decisions are possible
247298 """
248299 initial_policy_count = len (global_enforcer .get_policy ())
249-
250300 all_policies = global_enforcer .get_policy ()
251301 all_grouping_policies = global_enforcer .get_grouping_policy ()
252302
253303 self .assertEqual (initial_policy_count , 0 )
254304 self .assertEqual (len (all_policies ), 0 )
255305 self .assertEqual (len (all_grouping_policies ), 0 )
256306
257- @test_data (
307+ @ddt_data (
258308 Filter (v2 = ["lib:*" ]), # Load all library policies
259309 Filter (v2 = ["course:*" ]), # Load all course policies
260310 Filter (v2 = ["org:*" ]), # Load all organization policies
@@ -277,76 +327,87 @@ def test_filtered_policy_loading_variations(self, policy_filter):
277327
278328 global_enforcer .clear_policy ()
279329 global_enforcer .load_filtered_policy (policy_filter )
330+
280331 loaded_policies = global_enforcer .get_policy ()
281332
282333 self .assertEqual (initial_policy_count , 0 )
283334 self .assertGreaterEqual (len (loaded_policies ), 0 )
284335
285- def test_policy_reload_scenarios (self ):
286- """Test policy reloading in different scenarios.
287-
288- This demonstrates how policies can be reloaded when application
289- context changes or when fresh policy data is needed.
336+ def test_policy_clear_and_reload (self ):
337+ """Test clearing and reloading policies maintains consistency.
290338
291339 Expected result:
292- - Each reload operation works correctly
293- - Policy counts change appropriately
294- - No errors occur during transitions
340+ - Cleared enforcer has no policies
341+ - Reloading produces same count as initial load
295342 """
296343 self ._load_policies_for_scope ("lib:*" )
297- first_load_count = len (global_enforcer .get_policy ())
298- self .assertGreater (first_load_count , 0 )
344+ initial_load_count = len (global_enforcer .get_policy ())
345+
346+ self .assertGreater (initial_load_count , 0 )
299347
300348 global_enforcer .clear_policy ()
301349 cleared_count = len (global_enforcer .get_policy ())
350+
302351 self .assertEqual (cleared_count , 0 )
303352
304353 self ._load_policies_for_scope ("lib:*" )
305- reload_count = len (global_enforcer .get_policy ())
306- self .assertEqual (reload_count , first_load_count )
354+ reloaded_count = len (global_enforcer .get_policy ())
307355
308- self ._load_policies_for_role_management ("role:library_user" )
309- filtered_count = len (global_enforcer .get_policy ())
310- self .assertLessEqual (filtered_count , first_load_count )
356+ self .assertEqual (reloaded_count , initial_load_count )
311357
312- def test_multi_scope_filtering_demonstration (self ):
313- """Test filtering across multiple scopes to demonstrate effectiveness.
358+ @ddt_data (* LIBRARY_ROLES )
359+ def test_filtered_loading_by_role (self , role_name ):
360+ """Test loading policies filtered by specific role.
314361
315- This test shows that filtered loading actually works by comparing
316- policy counts when loading different scope combinations.
362+ Expected result:
363+ - Filtered count matches policies in file for that role
364+ - All loaded policies contain the specified role
365+ """
366+ expected_count = self ._count_policies_in_file (role = role_name )
367+
368+ self ._load_policies_for_role_management (role_name )
369+ loaded_policies = global_enforcer .get_policy ()
370+
371+ self .assertEqual (len (loaded_policies ), expected_count )
372+
373+ for policy in loaded_policies :
374+ self .assertIn (role_name , str (policy ))
375+
376+ def test_multi_scope_filtering (self ):
377+ """Test filtering across multiple scopes.
317378
318379 Expected result:
319- - Different scopes load different policy counts
320- - Combined scopes load sum of individual scopes
321- - Filtering is precise and predictable
380+ - Combined scope filter loads sum of individual scopes
381+ - Total load equals sum of all scope policies
322382 """
323- # Add test policies for multiple scopes
324- self ._add_test_policies_for_multiple_scopes ()
383+ lib_scope = "lib:*"
384+ course_scope = "course:*"
385+ org_scope = "org:*"
325386
326- # Load all policies to get baseline
327- global_enforcer .load_policy ()
328- total_policy_count = len (global_enforcer .get_policy ())
329- self .assertGreater (total_policy_count , 0 )
387+ expected_lib_count = self ._count_policies_in_file (scope_pattern = lib_scope )
388+ self ._add_test_policies_for_multiple_scopes ()
330389
331- # Test individual scope loading
332- self ._load_policies_for_scope ("lib:*" )
390+ self ._load_policies_for_scope (lib_scope )
333391 lib_count = len (global_enforcer .get_policy ())
334392
335- self ._load_policies_for_scope ("course:*" )
393+ self ._load_policies_for_scope (course_scope )
336394 course_count = len (global_enforcer .get_policy ())
337395
338- self ._load_policies_for_scope ("org:*" )
396+ self ._load_policies_for_scope (org_scope )
339397 org_count = len (global_enforcer .get_policy ())
340398
341- # Test combined scope loading
399+ self .assertEqual (lib_count , expected_lib_count )
400+ self .assertEqual (course_count , 6 )
401+ self .assertEqual (org_count , 3 )
402+
342403 global_enforcer .clear_policy ()
343- multi_scope_filter = Filter (v2 = ["lib:*" , "course:*" ])
344- global_enforcer .load_filtered_policy (multi_scope_filter )
404+ combined_filter = Filter (v2 = [lib_scope , course_scope ])
405+ global_enforcer .load_filtered_policy (combined_filter )
345406 combined_count = len (global_enforcer .get_policy ())
346407
347- # Verify filtering works as expected
348- self .assertEqual (lib_count , 4 )
349- self .assertEqual (course_count , 6 )
350- self .assertEqual (org_count , 3 )
351408 self .assertEqual (combined_count , lib_count + course_count )
352- self .assertEqual (total_policy_count , lib_count + course_count + org_count )
409+
410+ global_enforcer .load_policy ()
411+ total_count = len (global_enforcer .get_policy ())
412+
413+ self .assertEqual (total_count , lib_count + course_count + org_count )
0 commit comments