"""Unit tests for centralized permission checks. Tests all permission functions in app.core.permissions using mock User/Tree/Step objects. No database required. """ from unittest.mock import MagicMock from uuid import uuid4 from app.core.permissions import ( ROLE_HIERARCHY, can_access_tree, can_create_category, can_create_content, can_create_step_category, can_create_tag, can_delete_tree, can_edit_step, can_edit_tree, can_manage_category, can_manage_step_category, can_manage_tree_tags, can_view_step, get_effective_role, has_minimum_role, ) # --- Helpers --- def _make_user( account_role="engineer", is_super_admin=False, account_id=None, user_id=None, ): user = MagicMock() user.id = user_id or uuid4() user.account_role = account_role user.is_super_admin = is_super_admin user.account_id = account_id or uuid4() return user def _make_tree(author_id=None, account_id=None, is_default=False, is_public=False): tree = MagicMock() tree.author_id = author_id or uuid4() tree.account_id = account_id or uuid4() tree.is_default = is_default tree.is_public = is_public return tree def _make_step(created_by=None, visibility="team", account_id=None): step = MagicMock() step.created_by = created_by or uuid4() step.visibility = visibility step.account_id = account_id or uuid4() return step def _make_category(account_id=None): cat = MagicMock() cat.account_id = account_id or uuid4() return cat # --- Role Hierarchy --- class TestRoleHierarchy: def test_hierarchy_order(self): assert ROLE_HIERARCHY["super_admin"] > ROLE_HIERARCHY["owner"] assert ROLE_HIERARCHY["owner"] > ROLE_HIERARCHY["engineer"] assert ROLE_HIERARCHY["engineer"] > ROLE_HIERARCHY["viewer"] def test_get_effective_role_super_admin(self): user = _make_user(is_super_admin=True, account_role="engineer") assert get_effective_role(user) == "super_admin" def test_get_effective_role_owner(self): user = _make_user(account_role="owner") assert get_effective_role(user) == "owner" def test_get_effective_role_engineer(self): user = _make_user(account_role="engineer") assert get_effective_role(user) == "engineer" def test_get_effective_role_viewer(self): user = _make_user(account_role="viewer") assert get_effective_role(user) == "viewer" def test_has_minimum_role_super_admin_passes_all(self): user = _make_user(is_super_admin=True) for role in ["viewer", "engineer", "owner", "super_admin"]: assert has_minimum_role(user, role) def test_has_minimum_role_viewer_fails_engineer(self): user = _make_user(account_role="viewer") assert not has_minimum_role(user, "engineer") def test_has_minimum_role_engineer_passes_engineer(self): user = _make_user(account_role="engineer") assert has_minimum_role(user, "engineer") def test_has_minimum_role_unknown_role_returns_false(self): user = _make_user(account_role="unknown_role") assert not has_minimum_role(user, "engineer") # --- Content Creation --- class TestCanCreateContent: def test_engineer_can_create(self): assert can_create_content(_make_user(account_role="engineer")) def test_owner_can_create(self): assert can_create_content(_make_user(account_role="owner")) def test_super_admin_can_create(self): assert can_create_content(_make_user(is_super_admin=True)) def test_viewer_cannot_create(self): assert not can_create_content(_make_user(account_role="viewer")) # --- Tree Permissions --- class TestCanEditTree: def test_author_can_edit_own_tree(self): user = _make_user() tree = _make_tree(author_id=user.id) assert can_edit_tree(user, tree) def test_super_admin_can_edit_any_tree(self): user = _make_user(is_super_admin=True) tree = _make_tree() assert can_edit_tree(user, tree) def test_account_owner_can_edit_account_tree(self): acct = uuid4() user = _make_user(account_role="owner", account_id=acct) tree = _make_tree(account_id=acct) assert can_edit_tree(user, tree) def test_engineer_cannot_edit_others_tree(self): user = _make_user() tree = _make_tree() # different author and account assert not can_edit_tree(user, tree) def test_viewer_cannot_edit_any_tree(self): user = _make_user(account_role="viewer") tree = _make_tree(author_id=user.id) # even own tree assert not can_edit_tree(user, tree) def test_owner_cannot_edit_other_account_tree(self): user = _make_user(account_role="owner", account_id=uuid4()) tree = _make_tree(account_id=uuid4()) # different account assert not can_edit_tree(user, tree) def test_owner_with_none_account_cannot_edit(self): user = _make_user(account_role="owner", account_id=None) tree = _make_tree(account_id=None) assert not can_edit_tree(user, tree) class TestCanDeleteTree: def test_super_admin_can_delete(self): user = _make_user(is_super_admin=True) assert can_delete_tree(user, _make_tree()) def test_owner_cannot_delete(self): user = _make_user(account_role="owner") assert not can_delete_tree(user, _make_tree()) def test_engineer_cannot_delete(self): user = _make_user() assert not can_delete_tree(user, _make_tree(author_id=user.id)) class TestCanAccessTree: def test_default_tree_accessible_to_all(self): user = _make_user(account_role="viewer") tree = _make_tree(is_default=True) assert can_access_tree(user, tree) def test_public_tree_accessible_to_all(self): user = _make_user(account_role="viewer") tree = _make_tree(is_public=True) assert can_access_tree(user, tree) def test_author_can_access_own_tree(self): user = _make_user() tree = _make_tree(author_id=user.id) assert can_access_tree(user, tree) def test_same_account_can_access(self): acct = uuid4() user = _make_user(account_id=acct) tree = _make_tree(account_id=acct) assert can_access_tree(user, tree) def test_super_admin_can_access_any(self): user = _make_user(is_super_admin=True) tree = _make_tree() assert can_access_tree(user, tree) def test_different_account_cannot_access_private(self): user = _make_user(account_id=uuid4()) tree = _make_tree(account_id=uuid4()) assert not can_access_tree(user, tree) def test_none_account_cannot_access_by_account(self): user = _make_user(account_id=None) tree = _make_tree(account_id=None, is_public=False, is_default=False) assert not can_access_tree(user, tree) # --- Step Permissions --- class TestCanEditStep: def test_creator_can_edit(self): user = _make_user() step = _make_step(created_by=user.id) assert can_edit_step(user, step) def test_super_admin_can_edit_any(self): user = _make_user(is_super_admin=True) assert can_edit_step(user, _make_step()) def test_engineer_cannot_edit_others(self): user = _make_user() assert not can_edit_step(user, _make_step()) def test_viewer_cannot_edit(self): user = _make_user(account_role="viewer") step = _make_step(created_by=user.id) assert not can_edit_step(user, step) class TestCanViewStep: def test_public_step_visible_to_all(self): user = _make_user(account_role="viewer") step = _make_step(visibility="public") assert can_view_step(user, step) def test_private_step_visible_to_creator(self): user = _make_user() step = _make_step(visibility="private", created_by=user.id) assert can_view_step(user, step) def test_private_step_hidden_from_others(self): user = _make_user() step = _make_step(visibility="private") assert not can_view_step(user, step) def test_team_step_visible_to_same_account(self): acct = uuid4() user = _make_user(account_id=acct) step = _make_step(visibility="team", account_id=acct) assert can_view_step(user, step) def test_team_step_hidden_from_other_account(self): user = _make_user(account_id=uuid4()) step = _make_step(visibility="team", account_id=uuid4()) assert not can_view_step(user, step) def test_team_step_visible_to_super_admin(self): user = _make_user(is_super_admin=True, account_id=uuid4()) step = _make_step(visibility="team", account_id=uuid4()) assert can_view_step(user, step) def test_unknown_visibility_returns_false(self): user = _make_user() step = _make_step(visibility="unknown") assert not can_view_step(user, step) def test_team_step_none_account_returns_false(self): user = _make_user(account_id=None) step = _make_step(visibility="team", account_id=None) assert not can_view_step(user, step) # --- Tag Permissions --- class TestCanCreateTag: def test_super_admin_can_create_global(self): user = _make_user(is_super_admin=True) assert can_create_tag(user, account_id=None) def test_super_admin_can_create_any_account(self): user = _make_user(is_super_admin=True) assert can_create_tag(user, account_id=uuid4()) def test_engineer_can_create_own_account(self): user = _make_user() assert can_create_tag(user, account_id=user.account_id) def test_engineer_cannot_create_global(self): user = _make_user() assert not can_create_tag(user, account_id=None) def test_viewer_cannot_create(self): user = _make_user(account_role="viewer") assert not can_create_tag(user, account_id=user.account_id) # --- Category Permissions --- class TestCanManageCategory: def test_super_admin_can_manage_any(self): user = _make_user(is_super_admin=True) assert can_manage_category(user, _make_category()) def test_owner_can_manage_own_account(self): acct = uuid4() user = _make_user(account_role="owner", account_id=acct) assert can_manage_category(user, _make_category(account_id=acct)) def test_owner_cannot_manage_other_account(self): user = _make_user(account_role="owner") assert not can_manage_category(user, _make_category()) def test_engineer_cannot_manage(self): user = _make_user() assert not can_manage_category(user, _make_category()) class TestCanCreateCategory: def test_super_admin_can_create_global(self): assert can_create_category(_make_user(is_super_admin=True), None) def test_owner_can_create_for_own_account(self): acct = uuid4() user = _make_user(account_role="owner", account_id=acct) assert can_create_category(user, acct) def test_owner_cannot_create_for_other_account(self): user = _make_user(account_role="owner") assert not can_create_category(user, uuid4()) def test_engineer_cannot_create(self): assert not can_create_category(_make_user(), uuid4()) def test_owner_with_none_account_cannot_create(self): user = _make_user(account_role="owner", account_id=None) assert not can_create_category(user, None) # --- Tree Tag Permissions --- class TestCanManageTreeTags: def test_author_can_manage(self): user = _make_user() tree = _make_tree(author_id=user.id) assert can_manage_tree_tags(user, tree) def test_super_admin_can_manage(self): assert can_manage_tree_tags(_make_user(is_super_admin=True), _make_tree()) def test_owner_can_manage_account_tree(self): acct = uuid4() user = _make_user(account_role="owner", account_id=acct) tree = _make_tree(account_id=acct) assert can_manage_tree_tags(user, tree) def test_viewer_cannot_manage(self): user = _make_user(account_role="viewer") tree = _make_tree(author_id=user.id) assert not can_manage_tree_tags(user, tree) def test_engineer_cannot_manage_others(self): assert not can_manage_tree_tags(_make_user(), _make_tree()) # --- Step Category Permissions --- class TestCanManageStepCategory: def test_super_admin_can_manage(self): assert can_manage_step_category(_make_user(is_super_admin=True), _make_category()) def test_owner_can_manage_own_account(self): acct = uuid4() user = _make_user(account_role="owner", account_id=acct) assert can_manage_step_category(user, _make_category(account_id=acct)) def test_engineer_cannot_manage(self): assert not can_manage_step_category(_make_user(), _make_category()) class TestCanCreateStepCategory: def test_super_admin_can_create_global(self): assert can_create_step_category(_make_user(is_super_admin=True), None) def test_owner_can_create_for_own_account(self): acct = uuid4() user = _make_user(account_role="owner", account_id=acct) assert can_create_step_category(user, acct) def test_engineer_cannot_create(self): assert not can_create_step_category(_make_user(), uuid4())