@@ -346,3 +346,131 @@ def test_bulk_caching_empty_context(self):
346346 # Should not call expensive functions if no changes were made
347347 mock_team_update .assert_not_called ()
348348 mock_obj_update .assert_not_called ()
349+
350+ def test_bulk_caching_memory_safe_mode (self , rando , team , inv_rd , org_inv_rd , inventory , organization ):
351+ """Test that memory_safe=True mode doesn't store object roles but still recomputes"""
352+ with patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update :
353+
354+ with bulk_rbac_caching (memory_safe = True ):
355+ # Multiple assignments that would normally collect object roles
356+ inv_rd .give_permission (rando , inventory )
357+ org_inv_rd .give_permission (team , organization )
358+
359+ # Should not be called during bulk mode
360+ mock_obj_update .assert_not_called ()
361+
362+ # Should be called once with no specific object roles (full recomputation)
363+ mock_obj_update .assert_called_once_with ()
364+
365+ def test_bulk_caching_memory_safe_vs_normal_mode (self , rando , inv_rd , inventory ):
366+ """Test that memory_safe mode calls recomputation while normal mode passes specific roles"""
367+ # Test normal mode
368+ with patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update :
369+ with bulk_rbac_caching (memory_safe = False ):
370+ assignment = inv_rd .give_permission (rando , inventory )
371+
372+ # Should be called with specific object roles
373+ mock_obj_update .assert_called_once ()
374+ call_args = mock_obj_update .call_args
375+ object_roles = call_args .kwargs ['object_roles' ]
376+ assert assignment .object_role in object_roles
377+
378+ # Clean up the assignment
379+ inv_rd .remove_permission (rando , inventory )
380+
381+ # Test memory-safe mode
382+ with patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update :
383+ with bulk_rbac_caching (memory_safe = True ):
384+ inv_rd .give_permission (rando , inventory )
385+
386+ # Should be called with no specific object roles (full recomputation)
387+ mock_obj_update .assert_called_once_with ()
388+
389+ def test_bulk_caching_memory_safe_with_team_updates (self , rando , team , member_rd ):
390+ """Test that memory_safe mode works correctly with team updates"""
391+ with (
392+ patch ('ansible_base.rbac.triggers.compute_team_member_roles' ) as mock_team_update ,
393+ patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update ,
394+ ):
395+
396+ with bulk_rbac_caching (memory_safe = True ):
397+ # Assignment that affects team membership
398+ member_rd .give_permission (rando , team )
399+
400+ # Should not be called during bulk mode
401+ mock_team_update .assert_not_called ()
402+ mock_obj_update .assert_not_called ()
403+
404+ # Both should be called, but object role update should be full recomputation
405+ mock_team_update .assert_called_once ()
406+ mock_obj_update .assert_called_once_with ()
407+
408+ def test_bulk_caching_memory_safe_empty_context (self ):
409+ """Test that empty memory_safe context doesn't call cache functions"""
410+ with (
411+ patch ('ansible_base.rbac.triggers.compute_team_member_roles' ) as mock_team_update ,
412+ patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update ,
413+ ):
414+
415+ with bulk_rbac_caching (memory_safe = True ):
416+ # No operations performed
417+ pass
418+
419+ # Should not call expensive functions if no changes were made, even in memory_safe mode
420+ mock_team_update .assert_not_called ()
421+ mock_obj_update .assert_not_called ()
422+
423+ def test_bulk_caching_memory_safe_nested_contexts (self , rando , inv_rd , inventory ):
424+ """Test that nested contexts with memory_safe work correctly"""
425+ with patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update :
426+
427+ with bulk_rbac_caching (memory_safe = True ):
428+ inv_rd .give_permission (rando , inventory )
429+
430+ # Nested context (memory_safe is ignored in nested calls)
431+ with bulk_rbac_caching (memory_safe = False ):
432+ # Another assignment in nested context
433+ # Should still not trigger updates
434+ pass
435+
436+ # Still in outer context, should not be called yet
437+ mock_obj_update .assert_not_called ()
438+
439+ # Only called once when exiting outermost context, with full recomputation
440+ mock_obj_update .assert_called_once_with ()
441+
442+ def test_bulk_caching_memory_safe_with_removal (self , rando , inv_rd , inventory ):
443+ """Test memory_safe mode when object role gets deleted during removal"""
444+ # First give permission normally
445+ inv_rd .give_permission (rando , inventory )
446+
447+ with patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update :
448+
449+ with bulk_rbac_caching (memory_safe = True ):
450+ # Remove permission in bulk mode - this will delete the object role
451+ inv_rd .remove_permission (rando , inventory )
452+ mock_obj_update .assert_not_called ()
453+
454+ # Should not call recomputation since object role was deleted (nothing to update)
455+ # This matches the existing behavior in regular bulk mode
456+ mock_obj_update .assert_not_called ()
457+
458+ def test_bulk_caching_memory_safe_mixed_operations (self , rando , team , inv_rd , org_inv_rd , inventory , organization ):
459+ """Test memory_safe mode with mixed add/remove operations that result in net changes"""
460+ with patch ('ansible_base.rbac.triggers.compute_object_role_permissions' ) as mock_obj_update :
461+
462+ with bulk_rbac_caching (memory_safe = True ):
463+ # Add permissions (creates object roles)
464+ inv_rd .give_permission (rando , inventory )
465+ org_inv_rd .give_permission (team , organization )
466+
467+ # Remove one permission (may or may not delete object role)
468+ inv_rd .remove_permission (rando , inventory )
469+
470+ # Add it back
471+ inv_rd .give_permission (rando , inventory )
472+
473+ mock_obj_update .assert_not_called ()
474+
475+ # Should call full recomputation since we had net updates
476+ mock_obj_update .assert_called_once_with ()
0 commit comments