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>
422 lines
13 KiB
Python
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())
|