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