Files
TimeTracker/tests/test_role_module_visibility.py
T
Dries Peeters 807e6370ee feat(roles): add per-role module visibility (hide modules by role)
Admins can hide whole app modules (Analytics, Finance & Expenses, CRM, etc.)
per role so users in that role neither see them in the nav nor access them
by URL/API.

- Add Role.hidden_module_ids (JSON denylist) and migration
- Extend ModuleRegistry.is_enabled() with role-based hide check; module is
  hidden only if ALL of the user's roles hide it (super admins bypass)
- Add Module visibility section to role create/edit form with checkboxes
  by category; persist via hidden_modules form field
- Add tests for registry hide/allow semantics and route decorator 403

Closes #484

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-02 19:18:28 +01:00

61 lines
1.9 KiB
Python

import pytest
from app import db
from app.models import Role, Settings, User
from app.utils.module_registry import ModuleRegistry
@pytest.mark.unit
def test_module_registry_hides_module_if_all_roles_hide(app, user):
"""If all assigned roles hide a module, ModuleRegistry should disable it."""
ModuleRegistry.initialize_defaults()
settings = Settings.get_settings()
r = Role(name="hide_analytics_role", hidden_module_ids=["analytics"])
db.session.add(r)
db.session.commit()
user.add_role(r)
db.session.commit()
assert ModuleRegistry.is_enabled("analytics", settings, user) is False
@pytest.mark.unit
def test_module_registry_allows_module_if_any_role_allows(app, user):
"""If any assigned role allows a module, ModuleRegistry should keep it enabled."""
ModuleRegistry.initialize_defaults()
settings = Settings.get_settings()
r_hide = Role(name="hide_analytics_role", hidden_module_ids=["analytics"])
r_allow = Role(name="allow_all_role", hidden_module_ids=[])
db.session.add_all([r_hide, r_allow])
db.session.commit()
user.add_role(r_hide)
user.add_role(r_allow)
db.session.commit()
assert ModuleRegistry.is_enabled("analytics", settings, user) is True
@pytest.mark.integration
def test_module_enabled_decorator_blocks_hidden_module_route(app, user):
"""A hidden module should return 403 for an authenticated user (route decorator)."""
r = Role(name="hide_analytics_role", hidden_module_ids=["analytics"])
db.session.add(r)
db.session.commit()
user.add_role(r)
db.session.commit()
from flask_login import login_user
from werkzeug.exceptions import Forbidden
from app.routes.analytics import analytics_dashboard
with app.test_request_context("/analytics"):
login_user(user, remember=True)
with pytest.raises(Forbidden):
analytics_dashboard()