Some checks failed
Deploy Development / deploy (push) Successful in 40s
Test Suite / pytest-backend (push) Successful in 6s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 6s
Test Suite / playwright-tests (push) Failing after 27s
- Introduced new function `club_admin_shares_club_with_creator` to check club admin permissions for shared clubs. - Updated `can_manage_club_org` to incorporate new role checks. - Enhanced exercise deletion logic to include checks for club admin roles and shared club memberships. - Added new filters for exercise visibility and status in the ExercisesListPage, allowing users to exclude specific criteria. - Implemented functionality to save user-specific exercise list preferences, improving user experience. - Updated API interactions to support new filtering options and preferences for exercise management.
132 lines
4.5 KiB
Python
132 lines
4.5 KiB
Python
"""
|
|
DELETE /api/exercises/{id}: Mandanten-/Rollenlogik und Verwendungsblock (409).
|
|
|
|
TestClient mit Overrides für Auth und TenantContext; DB via get_db/get_cursor gemockt.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
os.environ.setdefault("SKIP_DB_MIGRATE", "1")
|
|
|
|
from auth import require_auth
|
|
from main import app
|
|
from tenant_context import TenantContext, get_tenant_context
|
|
|
|
|
|
@pytest.fixture
|
|
def client() -> TestClient:
|
|
return TestClient(app)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def _clear_overrides() -> None:
|
|
yield
|
|
app.dependency_overrides.pop(require_auth, None)
|
|
app.dependency_overrides.pop(get_tenant_context, None)
|
|
|
|
|
|
def _mock_db_cm(mock_cur: MagicMock):
|
|
mock_conn = MagicMock()
|
|
mock_cm = MagicMock()
|
|
mock_cm.__enter__.return_value = mock_conn
|
|
mock_cm.__exit__.return_value = False
|
|
return mock_cm
|
|
|
|
|
|
def test_delete_trainer_private_own_ok(client: TestClient) -> None:
|
|
mock_cur = MagicMock()
|
|
mock_cur.fetchone.side_effect = [
|
|
{"id": 7, "created_by": 42, "visibility": "private", "club_id": None},
|
|
{"block_items": 0, "section_items": 0, "prog_edges": 0},
|
|
]
|
|
mock_cm = _mock_db_cm(mock_cur)
|
|
|
|
app.dependency_overrides[require_auth] = lambda: {"profile_id": 42, "role": "trainer"}
|
|
app.dependency_overrides[get_tenant_context] = lambda: TenantContext(
|
|
profile_id=42,
|
|
global_role="trainer",
|
|
effective_club_id=5,
|
|
club_ids=frozenset({5}),
|
|
memberships=[],
|
|
)
|
|
with patch("routers.exercises.get_db", return_value=mock_cm), patch(
|
|
"routers.exercises.get_cursor", return_value=mock_cur
|
|
):
|
|
r = client.delete("/api/exercises/7", headers={"X-Auth-Token": "dummy"})
|
|
assert r.status_code == 200
|
|
assert r.json().get("ok") is True
|
|
|
|
|
|
def test_delete_trainer_club_exercise_forbidden_without_club_admin(client: TestClient) -> None:
|
|
mock_cur = MagicMock()
|
|
mock_cur.fetchone.side_effect = [
|
|
{"id": 7, "created_by": 42, "visibility": "club", "club_id": 5},
|
|
]
|
|
mock_cm = _mock_db_cm(mock_cur)
|
|
|
|
app.dependency_overrides[require_auth] = lambda: {"profile_id": 42, "role": "trainer"}
|
|
app.dependency_overrides[get_tenant_context] = lambda: TenantContext(
|
|
profile_id=42,
|
|
global_role="trainer",
|
|
effective_club_id=5,
|
|
club_ids=frozenset({5}),
|
|
memberships=[],
|
|
)
|
|
with patch("routers.exercises.get_db", return_value=mock_cm), patch(
|
|
"routers.exercises.get_cursor", return_value=mock_cur
|
|
), patch("routers.exercises.has_club_role", return_value=False):
|
|
r = client.delete("/api/exercises/7", headers={"X-Auth-Token": "dummy"})
|
|
assert r.status_code == 403
|
|
|
|
|
|
def test_delete_usage_returns_409(client: TestClient) -> None:
|
|
mock_cur = MagicMock()
|
|
mock_cur.fetchone.side_effect = [
|
|
{"id": 7, "created_by": 42, "visibility": "private", "club_id": None},
|
|
{"block_items": 1, "section_items": 2, "prog_edges": 3},
|
|
]
|
|
mock_cm = _mock_db_cm(mock_cur)
|
|
|
|
app.dependency_overrides[require_auth] = lambda: {"profile_id": 42, "role": "trainer"}
|
|
app.dependency_overrides[get_tenant_context] = lambda: TenantContext(
|
|
profile_id=42,
|
|
global_role="trainer",
|
|
effective_club_id=None,
|
|
club_ids=frozenset(),
|
|
memberships=[],
|
|
)
|
|
with patch("routers.exercises.get_db", return_value=mock_cm), patch(
|
|
"routers.exercises.get_cursor", return_value=mock_cur
|
|
):
|
|
r = client.delete("/api/exercises/7", headers={"X-Auth-Token": "dummy"})
|
|
assert r.status_code == 409
|
|
detail = r.json().get("detail", "")
|
|
assert "Übungsblöcken" in detail or "Trainingsplänen" in detail
|
|
|
|
|
|
def test_delete_official_forbidden_non_platform_admin(client: TestClient) -> None:
|
|
mock_cur = MagicMock()
|
|
mock_cur.fetchone.side_effect = [
|
|
{"id": 99, "created_by": 1, "visibility": "official", "club_id": None},
|
|
]
|
|
mock_cm = _mock_db_cm(mock_cur)
|
|
|
|
app.dependency_overrides[require_auth] = lambda: {"profile_id": 42, "role": "trainer"}
|
|
app.dependency_overrides[get_tenant_context] = lambda: TenantContext(
|
|
profile_id=42,
|
|
global_role="trainer",
|
|
effective_club_id=None,
|
|
club_ids=frozenset(),
|
|
memberships=[],
|
|
)
|
|
with patch("routers.exercises.get_db", return_value=mock_cm), patch(
|
|
"routers.exercises.get_cursor", return_value=mock_cur
|
|
):
|
|
r = client.delete("/api/exercises/99", headers={"X-Auth-Token": "dummy"})
|
|
assert r.status_code == 403
|