shinkan-jinkendo/backend/tests/test_access_layer.py
Lars b464047c3a
All checks were successful
Deploy Development / deploy (push) Successful in 50s
Test Suite / pytest-backend (push) Successful in 44s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 14s
Test Suite / k6 /health Baseline (push) Successful in 33s
Test Suite / playwright-tests (push) Successful in 1m16s
Enhance Exercise Progression Graph Functionality and Visibility Logic
- Introduced new functions for handling exercise visibility in progression graphs, including `library_content_visibility_for_progression_graph_sql` to manage visibility based on graph context.
- Added `_supplemental_exercise_ids_from_body` to extract exercise IDs from request bodies, improving data handling in path suggestions.
- Implemented visibility promotion candidate retrieval in the API, allowing for the identification of private exercises that need visibility adjustments when promoting graph visibility.
- Enhanced existing SQL queries and retrieval functions to incorporate new visibility logic, ensuring accurate exercise visibility based on user roles and graph settings.
- Updated frontend components to support visibility promotion workflows, including user prompts for managing private exercises during graph visibility changes.
- Added tests to validate new visibility logic and ensure robustness in exercise retrieval and promotion processes.
2026-06-11 12:10:46 +02:00

204 lines
5.9 KiB
Python

"""Unit tests ohne Datenbank für die Zugriffsschicht (Visibility-SQL, Header-Parsing)."""
import pytest
from fastapi import HTTPException
from tenant_context import (
library_content_visibility_for_progression_graph_sql,
library_content_visibility_sql,
parse_active_club_header,
resolve_tenant_context,
)
def test_library_visibility_sql_platform_admin_restricts_club_by_membership():
sql, params = library_content_visibility_sql(
alias="e",
profile_id=1,
role="admin",
effective_club_id=None,
)
assert "e.visibility = 'official'" in sql
assert "club_members" in sql
assert params == [1, 1]
def test_library_visibility_sql_superadmin_uses_membership_clause_for_club_visibility():
sql, params = library_content_visibility_sql(
alias="fp",
profile_id=2,
role="superadmin",
effective_club_id=100,
)
assert "club_members" in sql
assert params == [2, 2]
def test_library_visibility_sql_trainer_without_active_club_no_shared_club_branch():
sql, params = library_content_visibility_sql(
alias="g",
profile_id=42,
role="trainer",
effective_club_id=None,
)
assert "official" in sql
assert "private" in sql
assert "visibility = 'club'" not in sql
assert params == [42]
def test_progression_graph_visibility_sql_private_matches_library():
base_sql, base_params = library_content_visibility_sql(
alias="e", profile_id=5, role="trainer", effective_club_id=12
)
graph_sql, graph_params = library_content_visibility_for_progression_graph_sql(
alias="e",
profile_id=5,
role="trainer",
effective_club_id=12,
graph_visibility="private",
graph_club_id=None,
)
assert graph_sql == base_sql
assert graph_params == base_params
def test_progression_graph_visibility_sql_club_excludes_private():
sql, params = library_content_visibility_for_progression_graph_sql(
alias="e",
profile_id=5,
role="trainer",
effective_club_id=12,
graph_visibility="club",
graph_club_id=12,
)
assert "official" in sql
assert "visibility = 'club'" in sql
assert "visibility = 'private'" not in sql
assert 12 in params
def test_library_visibility_sql_user_with_active_club_includes_club_branch():
sql, params = library_content_visibility_sql(
alias="t",
profile_id=7,
role="user",
effective_club_id=99,
)
assert "visibility = 'club'" in sql
assert "club_members" in sql
assert params[0] == 7 # private branch created_by
assert 99 in params
assert params.count(7) >= 2 # private + EXISTS membership
def test_parse_active_club_header_none_and_empty():
assert parse_active_club_header(None) is None
assert parse_active_club_header("") is None
assert parse_active_club_header(" ") is None
def test_parse_active_club_header_valid():
assert parse_active_club_header("12") == 12
def test_parse_active_club_header_invalid():
with pytest.raises(HTTPException) as exc:
parse_active_club_header("not-int")
assert exc.value.status_code == 400
def test_parse_active_club_header_non_positive():
with pytest.raises(HTTPException) as exc:
parse_active_club_header("0")
assert exc.value.status_code == 400
def test_resolve_platform_admin_uses_stored_club_without_header(monkeypatch):
"""Ohne X-Active-Club-Id: effective wie gespeichertes Profil (Sync mit Dropdown/DB)."""
cur = object()
def fake_exists(c, cid):
return cid == 99
monkeypatch.setattr("tenant_context._club_exists", fake_exists)
ctx = resolve_tenant_context(
cur,
profile_id=1,
global_role="admin",
header_raw=None,
memberships=[{"id": 10}],
stored_active_club_id=99,
email_verified=True,
)
assert ctx.effective_club_id == 99
def test_resolve_platform_admin_header_overrides_stored(monkeypatch):
cur = object()
def fake_exists(c, cid):
return cid in (5, 99)
monkeypatch.setattr("tenant_context._club_exists", fake_exists)
ctx = resolve_tenant_context(
cur,
profile_id=1,
global_role="superadmin",
header_raw="5",
memberships=[{"id": 10}],
stored_active_club_id=99,
email_verified=True,
)
assert ctx.effective_club_id == 5
def test_resolve_platform_admin_no_header_stored_invalid(monkeypatch):
cur = object()
monkeypatch.setattr("tenant_context._club_exists", lambda c, cid: False)
ctx = resolve_tenant_context(
cur,
profile_id=1,
global_role="admin",
header_raw=None,
memberships=[{"id": 1}],
stored_active_club_id=123,
email_verified=True,
)
assert ctx.effective_club_id is None
def test_resolve_trainer_club_ids_excludes_inactive_memberships():
"""Nur aktive Vereinszugänge zählen für Mandant / Header-Validierung."""
cur = object()
ctx = resolve_tenant_context(
cur,
profile_id=9,
global_role="user",
header_raw=None,
memberships=[
{"id": 10, "membership_status": "inactive"},
{"id": 20, "membership_status": "active"},
],
stored_active_club_id=None,
invalid_header_policy="ignore",
email_verified=True,
)
assert ctx.club_ids == frozenset({20})
assert ctx.effective_club_id == 20
def test_resolve_all_memberships_inactive_no_effective_club():
cur = object()
ctx = resolve_tenant_context(
cur,
profile_id=9,
global_role="user",
header_raw=None,
memberships=[{"id": 10, "membership_status": "inactive"}],
stored_active_club_id=10,
invalid_header_policy="ignore",
email_verified=True,
)
assert ctx.club_ids == frozenset()
assert ctx.effective_club_id is None