-
Notifications
You must be signed in to change notification settings - Fork 0
Increased branch coverage for question_services, rights_manager, and … #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -164,7 +164,8 @@ def _save_activity_rights( | |
| committer_id, activity_rights, commit_message, commit_cmds | ||
| ) | ||
| return | ||
| elif activity_type == constants.ACTIVITY_TYPE_COLLECTION: | ||
| else: | ||
| assert activity_type == constants.ACTIVITY_TYPE_COLLECTION | ||
|
Comment on lines
+167
to
+168
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's locate and read the file
find . -name "rights_manager.py" -type f | head -5Repository: jayam04/oppia Length of output: 89 🏁 Script executed: # Read the specific section around line 167
sed -n '155,185p' core/domain/rights_manager.py | cat -nRepository: jayam04/oppia Length of output: 1468 🏁 Script executed: # Also check the other mentioned line ranges
echo "=== Lines 275-290 ===" && sed -n '275,290p' core/domain/rights_manager.py | cat -n
echo "=== Lines 418-430 ===" && sed -n '418,430p' core/domain/rights_manager.py | cat -n
echo "=== Lines 1156-1170 ===" && sed -n '1156,1170p' core/domain/rights_manager.py | cat -n
echo "=== Lines 1384-1400 ===" && sed -n '1384,1400p' core/domain/rights_manager.py | cat -nRepository: jayam04/oppia Length of output: 2875 🏁 Script executed: # Check the function signature and broader context around _save_activity_rights
sed -n '150,200p' core/domain/rights_manager.py | cat -nRepository: jayam04/oppia Length of output: 2351 Remove unnecessary The Proposed fix if activity_type == constants.ACTIVITY_TYPE_EXPLORATION:
assert isinstance(activity_rights, exp_rights_domain.ExplorationRights)
_save_exploration_rights(
committer_id, activity_rights, commit_message, commit_cmds
)
return
- else:
- assert activity_type == constants.ACTIVITY_TYPE_COLLECTION
- model = collection_models.CollectionRightsModel.get(
- activity_rights.id, strict=True
- )
+ assert activity_type == constants.ACTIVITY_TYPE_COLLECTION
+ model = collection_models.CollectionRightsModel.get(
+ activity_rights.id, strict=True
+ )Per PEP 8: avoid unnecessary nesting and indentation after control flow statements that terminate execution. 🧰 Tools🪛 Ruff (0.15.11)[warning] 167-167: Unnecessary Remove unnecessary (RET505) 🤖 Prompt for AI Agents |
||
| model = collection_models.CollectionRightsModel.get( | ||
| activity_rights.id, strict=True | ||
| ) | ||
|
|
@@ -276,7 +277,8 @@ def _update_activity_summary( | |
| if activity_type == constants.ACTIVITY_TYPE_EXPLORATION: | ||
| assert isinstance(activity_rights, exp_rights_domain.ExplorationRights) | ||
| _update_exploration_summary(activity_rights) | ||
| elif activity_type == constants.ACTIVITY_TYPE_COLLECTION: | ||
| else: | ||
| assert activity_type == constants.ACTIVITY_TYPE_COLLECTION | ||
| _update_collection_summary(activity_rights) | ||
|
Comment on lines
+280
to
282
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# 1) Check whether optimized Python mode might be used anywhere.
rg -n --iglob '*.yml' --iglob '*.yaml' --iglob '*.sh' --iglob '*.py' \
'PYTHONOPTIMIZE|python\s+-O|python3\s+-O'
# 2) Enumerate callsites of the affected helpers.
rg -nP '\b(_update_activity_summary|_change_activity_status)\s*\('Repository: jayam04/oppia Length of output: 697 🏁 Script executed: sed -n '275,290p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 633 🏁 Script executed: sed -n '1385,1400p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 769 🏁 Script executed: sed -n '260,285p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 987 🏁 Script executed: sed -n '1358,1375p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 620 🏁 Script executed: # Check all callsites of _update_activity_summary
sed -n '1195,1210p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 457 🏁 Script executed: # Check callsite around line 1300
sed -n '1295,1310p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 538 🏁 Script executed: # Check callsite around line 1357
sed -n '1352,1365p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 406 🏁 Script executed: # Check callsites of _change_activity_status
sed -n '1437,1450p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 463 🏁 Script executed: # Check the other _change_activity_status callsite
sed -n '1475,1488p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 544 🏁 Script executed: # Find the publish_activity and unpublish_activity function signatures
rg -n 'def (publish_activity|unpublish_activity)' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 39 🏁 Script executed: # Look at the beginning of publish_activity to see if activity_type is validated
sed -n '1410,1450p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 1461 🏁 Script executed: # Look at unpublish_activity
sed -n '1460,1500p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 1428 🏁 Script executed: # Find _get_activity_rights to see if it validates activity_type
rg -n 'def _get_activity_rights' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 227 🏁 Script executed: # Read _get_activity_rights implementation
sed -n '220,260p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 1271 🏁 Script executed: # Look for any other functions that accept activity_type and might have it unvalidated
rg -n 'activity_type:' core/domain/rights_manager.py | head -30Repository: jayam04/oppia Length of output: 1613 🏁 Script executed: # Read _get_activity_rights implementation
sed -n '656,700p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 1795 🏁 Script executed: # Check if any validation happens in calling functions - look at who calls _publish_activity/_unpublish_activity
rg -n '(publish_activity|unpublish_activity|_assign_role)\(' core/domain/rights_manager.py | head -15Repository: jayam04/oppia Length of output: 595 🏁 Script executed: # Check the public API entry points like assign_role_for_exploration
sed -n '1500,1550p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 1580 🏁 Script executed: # Check _deassign_role implementation
sed -n '1203,1280p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 3096 🏁 Script executed: # Check _release_ownership_of_activity implementation
sed -n '1305,1360p' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 1923 🏁 Script executed: # Verify the call sequence - check if _get_activity_rights is always called before _update_activity_summary
grep -B 10 '_update_activity_summary' core/domain/rights_manager.py | grep -E '(_get_activity_rights|_update_activity_summary)' | head -20Repository: jayam04/oppia Length of output: 331 🏁 Script executed: # Double-check the exception handling in _get_activity_rights to confirm it raises for invalid types
sed -n '656,690p' core/domain/rights_manager.py | grep -A 20 'else:'Repository: jayam04/oppia Length of output: 231 🏁 Script executed: # Verify if _update_activity_summary and _change_activity_status can be called directly
# Check if they are marked as private (leading underscore) and search for direct calls without _get_activity_rights
rg -n '_update_activity_summary|_change_activity_status' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 457 🏁 Script executed: # Check if there are any public API functions that might call these directly
rg -n '^def [a-z].*activity_type' core/domain/rights_manager.pyRepository: jayam04/oppia Length of output: 39 Replace assert discriminator checks with explicit exception handling. At lines 281 and 1390, the code uses Consider the suggested fix to replace both 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
|
|
@@ -418,7 +420,8 @@ def _get_activity_rights_where_user_is_owner( | |
| exp_models.ExplorationRightsModel.owner_ids == user_id | ||
| ) | ||
| ).fetch() | ||
| elif activity_type == constants.ACTIVITY_TYPE_COLLECTION: | ||
| else: | ||
| assert activity_type == constants.ACTIVITY_TYPE_COLLECTION | ||
| activity_rights_models = collection_models.CollectionRightsModel.query( | ||
| datastore_services.any_of( | ||
| collection_models.CollectionRightsModel.owner_ids == user_id | ||
|
|
@@ -1155,7 +1158,8 @@ def _assign_role( | |
| activity_rights.viewer_ids.remove(assignee_id) | ||
| old_role = rights_domain.ROLE_VIEWER | ||
|
|
||
| elif new_role == rights_domain.ROLE_VIEWER: | ||
| else: | ||
| assert new_role == rights_domain.ROLE_VIEWER | ||
|
|
||
| if ( | ||
| activity_rights.is_owner(assignee_id) | ||
|
|
@@ -1382,7 +1386,8 @@ def _change_activity_status( | |
| activity_rights.status = new_status | ||
| if activity_type == constants.ACTIVITY_TYPE_EXPLORATION: | ||
| cmd_type = rights_domain.CMD_CHANGE_EXPLORATION_STATUS | ||
| elif activity_type == constants.ACTIVITY_TYPE_COLLECTION: | ||
| else: | ||
| assert activity_type == constants.ACTIVITY_TYPE_COLLECTION | ||
| cmd_type = rights_domain.CMD_CHANGE_COLLECTION_STATUS | ||
| commit_cmds = [ | ||
| {'cmd': cmd_type, 'old_status': old_status, 'new_status': new_status} | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -218,6 +218,19 @@ def test_check_can_modify_core_activity_roles_for_none_activity( | |
| ) | ||
| ) | ||
|
|
||
| def test_check_can_modify_core_activity_roles_owned_action_but_not_owner( | ||
| self, | ||
| ) -> None: | ||
| # user_a has ACTION_MODIFY_CORE_ROLES_FOR_OWNED_ACTIVITY but is not | ||
| # the owner of exp_id, so the function should return False. | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_b) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_modify_core_activity_roles( | ||
| self.user_a, exp_rights | ||
| ) | ||
| ) | ||
|
Comment on lines
+221
to
+232
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Make this non-owner branch test independent of default role permissions. At Line 229, this uses ✅ Deterministic test setup def test_check_can_modify_core_activity_roles_owned_action_but_not_owner(
self,
) -> None:
- # user_a has ACTION_MODIFY_CORE_ROLES_FOR_OWNED_ACTIVITY but is not
- # the owner of exp_id, so the function should return False.
self.save_new_valid_exploration(self.EXP_ID, self.user_id_b)
exp_rights = rights_manager.get_exploration_rights(self.EXP_ID)
+ mock_user = unittest.mock.MagicMock(spec=user_domain.UserActionsInfo)
+ mock_user.user_id = self.user_id_a
+ mock_user.actions = [
+ role_services.ACTION_MODIFY_CORE_ROLES_FOR_OWNED_ACTIVITY
+ ]
self.assertFalse(
rights_manager.check_can_modify_core_activity_roles(
- self.user_a, exp_rights
+ mock_user, exp_rights
)
)🤖 Prompt for AI Agents |
||
|
|
||
| def test_non_splash_page_demo_exploration(self) -> None: | ||
| # Note: there is no difference between permissions for demo | ||
| # explorations, whether or not they are on the splash page. | ||
|
|
@@ -1287,6 +1300,188 @@ def test_get_activity_rights_where_user_is_owner_for_exploration( | |
| self.assertEqual(activity_rights_list[0].id, 'exp1') | ||
| self.assertTrue(activity_rights_list[0].is_owner(owner_id)) | ||
|
|
||
| def test_get_exploration_rights_where_user_is_owner(self) -> None: | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| owned = rights_manager.get_exploration_rights_where_user_is_owner( | ||
| self.user_id_a | ||
| ) | ||
| self.assertEqual(len(owned), 1) | ||
| self.assertEqual(owned[0].id, self.EXP_ID) | ||
|
|
||
| def test_exploration_status_helpers(self) -> None: | ||
| exp = exp_domain.Exploration.create_default_exploration( | ||
| self.EXP_ID, title='A title', category='A category' | ||
| ) | ||
| exp_services.save_new_exploration(self.user_id_a, exp) | ||
| self.assertTrue(rights_manager.is_exploration_private(self.EXP_ID)) | ||
| self.assertFalse(rights_manager.is_exploration_public(self.EXP_ID)) | ||
| self.assertFalse(rights_manager.is_exploration_cloned(self.EXP_ID)) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| self.assertFalse(rights_manager.is_exploration_private(self.EXP_ID)) | ||
| self.assertTrue(rights_manager.is_exploration_public(self.EXP_ID)) | ||
|
|
||
| def test_check_can_functions_return_false_for_none_activity_rights( | ||
| self, | ||
| ) -> None: | ||
| self.assertFalse( | ||
| rights_manager.check_can_access_activity(self.user_a, None) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.check_can_edit_activity(self.user_a, None) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.check_can_voiceover_activity(self.user_a, None) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.check_can_delete_activity(self.user_a, None) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.check_can_release_ownership(self.user_a, None) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.check_can_publish_activity(self.user_a, None) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.check_can_unpublish_activity(self.user_a, None) | ||
| ) | ||
|
|
||
| def test_check_can_edit_activity_returns_false_without_edit_action( | ||
| self, | ||
| ) -> None: | ||
| guest_user = user_services.get_user_actions_info(None) | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_edit_activity(guest_user, exp_rights) | ||
| ) | ||
|
|
||
| def test_check_can_voiceover_activity_returns_false_without_edit_action( | ||
| self, | ||
| ) -> None: | ||
| guest_user = user_services.get_user_actions_info(None) | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_voiceover_activity(guest_user, exp_rights) | ||
| ) | ||
|
|
||
| def test_check_can_modify_core_roles_returns_false_for_community_owned( | ||
| self, | ||
| ) -> None: | ||
| exp = exp_domain.Exploration.create_default_exploration( | ||
| self.EXP_ID, title='A title', category='A category' | ||
| ) | ||
| exp_services.save_new_exploration(self.user_id_a, exp) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| rights_manager.release_ownership_of_exploration( | ||
| self.user_a, self.EXP_ID | ||
| ) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_modify_core_activity_roles( | ||
| self.user_a, exp_rights | ||
| ) | ||
| ) | ||
|
|
||
| def test_check_can_modify_core_roles_returns_false_without_owned_action( | ||
| self, | ||
| ) -> None: | ||
| # Use a mock user with no actions to force the outer `if` at the | ||
| # ACTION_MODIFY_CORE_ROLES_FOR_OWNED_ACTIVITY check to be False. | ||
| mock_user = unittest.mock.MagicMock(spec=user_domain.UserActionsInfo) | ||
| mock_user.user_id = self.user_id_a | ||
| mock_user.actions = [] | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_modify_core_activity_roles( | ||
| mock_user, exp_rights | ||
| ) | ||
| ) | ||
|
|
||
| def test_check_can_publish_returns_false_for_cloned_activity( | ||
| self, | ||
| ) -> None: | ||
| mock_rights = unittest.mock.MagicMock() | ||
| mock_rights.cloned_from = 'some_exploration_id' | ||
| self.assertFalse( | ||
| rights_manager.check_can_publish_activity(self.user_a, mock_rights) | ||
| ) | ||
|
|
||
| def test_check_can_publish_returns_false_without_publish_action( | ||
| self, | ||
| ) -> None: | ||
| # Use a mock user with no actions to force the outer `if` at the | ||
| # ACTION_PUBLISH_OWNED_ACTIVITY check to be False. | ||
| mock_user = unittest.mock.MagicMock(spec=user_domain.UserActionsInfo) | ||
| mock_user.user_id = self.user_id_b | ||
| mock_user.actions = [] | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_publish_activity(mock_user, exp_rights) | ||
| ) | ||
|
|
||
| def test_check_can_unpublish_returns_false_for_community_owned( | ||
| self, | ||
| ) -> None: | ||
| exp = exp_domain.Exploration.create_default_exploration( | ||
| self.EXP_ID, title='A title', category='A category' | ||
| ) | ||
| exp_services.save_new_exploration(self.user_id_a, exp) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| rights_manager.release_ownership_of_exploration( | ||
| self.user_a, self.EXP_ID | ||
| ) | ||
| exp_rights = rights_manager.get_exploration_rights(self.EXP_ID) | ||
| self.assertFalse( | ||
| rights_manager.check_can_unpublish_activity( | ||
| self.user_moderator, exp_rights | ||
| ) | ||
| ) | ||
|
|
||
| def test_release_ownership_raises_for_unauthorized_user(self) -> None: | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| with self.assertRaisesRegex( | ||
| Exception, | ||
| 'The ownership of this exploration cannot be released.', | ||
| ): | ||
| rights_manager.release_ownership_of_exploration( | ||
| self.user_b, self.EXP_ID | ||
| ) | ||
|
|
||
| def test_publish_raises_for_unauthorized_user(self) -> None: | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| with self.assertRaisesRegex( | ||
| Exception, 'This exploration cannot be published.' | ||
| ): | ||
| rights_manager.publish_exploration(self.user_b, self.EXP_ID) | ||
|
|
||
| def test_unpublish_raises_for_unauthorized_user(self) -> None: | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| with self.assertRaisesRegex( | ||
| Exception, 'This exploration cannot be unpublished.' | ||
| ): | ||
| rights_manager.unpublish_exploration(self.user_a, self.EXP_ID) | ||
|
|
||
| def test_republish_does_not_reset_first_published_msec(self) -> None: | ||
| self.save_new_valid_exploration(self.EXP_ID, self.user_id_a) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| rights_manager.unpublish_exploration(self.user_moderator, self.EXP_ID) | ||
| first_published = rights_manager.get_exploration_rights( | ||
| self.EXP_ID | ||
| ).first_published_msec | ||
| self.assertIsNotNone(first_published) | ||
| rights_manager.publish_exploration(self.user_a, self.EXP_ID) | ||
| self.assertEqual( | ||
| rights_manager.get_exploration_rights( | ||
| self.EXP_ID | ||
| ).first_published_msec, | ||
| first_published, | ||
| ) | ||
|
|
||
|
|
||
| class CollectionRightsTests(test_utils.GenericTestBase): | ||
| """Test that rights for actions on collections work as expected.""" | ||
|
|
@@ -2132,6 +2327,20 @@ def test_get_collection_rights_where_user_is_owner(self) -> None: | |
| self.assertEqual(owned_rights[0].id, 'col1') | ||
| self.assertTrue(owned_rights[0].is_owner(self.user_id_a)) | ||
|
|
||
| def test_collection_status_helpers(self) -> None: | ||
| self.save_new_valid_collection(self.COLLECTION_ID, self.user_id_a) | ||
| self.assertTrue( | ||
| rights_manager.is_collection_private(self.COLLECTION_ID) | ||
| ) | ||
| self.assertFalse( | ||
| rights_manager.is_collection_public(self.COLLECTION_ID) | ||
| ) | ||
| rights_manager.publish_collection(self.user_a, self.COLLECTION_ID) | ||
| self.assertFalse( | ||
| rights_manager.is_collection_private(self.COLLECTION_ID) | ||
| ) | ||
| self.assertTrue(rights_manager.is_collection_public(self.COLLECTION_ID)) | ||
|
|
||
|
|
||
| class CheckCanReleaseOwnershipTest(test_utils.GenericTestBase): | ||
| """Tests for check_can_release_ownership function.""" | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Assertion is too weak for “unchanged question” behavior.
At Line 750, checking only
idwill pass even if other fields were mutated. Capture pre-state and assert full equality after applying the change list.🔍 Suggested assertion upgrade
📝 Committable suggestion
🤖 Prompt for AI Agents