Files
resolutionflow/backend/tests/test_permissions_unit.py
chihlasm 064bc0aa48 test: add 113 unit tests for permissions, tree validation, and settings
Cover all permission functions (59 tests), tree validation logic (25 tests),
settings manager parse/infer helpers (21 tests), and Stripe webhook stubs (8 tests).
Key modules now at 100% coverage: permissions.py, tree_validation.py, stripe_handlers.py.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-08 17:58:48 -05:00

422 lines
13 KiB
Python

"""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())