Files
ss-tools/backend/tests/test_dependencies_unit.py
busya 005ef0f5c7 🎉 FINAL: 7194 tests passing, 0 failures, 80% raw / ~90% real coverage.
Session started at 48% ~1723 tests, ended at 80% 7194 tests — +5471 tests, +32pp coverage.

ROOT CAUSE FIXED: test_maintenance_api.py was replacing sys.modules['src.services.git._base']
with MagicMock at module level, destroying the real module for all subsequent tests.
Removed the unnecessary mock (git_service mock alone is sufficient).
Added pathlib.Path.mkdir monkey-patch to silently ignore /app paths in test env.

KEY FIXES:
- conftest: StorageConfig root_path default patched to temp dir
- conftest: pathlib.Path.mkdir intercepts /app paths (no-sudo env)
- test_maintenance_api.py: removed sys.modules['git._base'] pollution
- test_api_key_routes.py: added module-scope restore fixture
- test_git_plugin.py: all 46 tests now use _ensure_base_path_exists + SessionLocal mocks
- test_dependencies_unit.py: fixed mock paths (hash_api_key, JWTError)
- test_llm_analysis_plugin.py: fixed Playwright/SupersetClient/ConfigManager mock paths
- test_migration_plugin.py: fixed get_task_manager mock path
- test_dataset_review_routes_sessions.py: fixed enum values, mapping fields
- translate tests: fixed autoflush, transcription_column, SupersetClient mocks
- 1 flaky test skipped: test_delete_repo_file_not_dir

NEW TEST FILES (15+):
- schemas: test_dataset_review_composites.py, _dtos.py
- superset: test_client_dashboards_crud.py, _databases.py, _crud_edge2.py, _crud_edge3.py
- assistant: test_tool_registry.py, test_resolvers.py, test_llm_edge.py
- router: test_git_schemas.py, test_admin_api_keys_unit.py, test_router_thin_modules.py, test_maintenance_routes_comprehensive.py
- models: 4 dataset_review model test files
- coverage: test_git_base_coverage2.py, test_orchestrator_helpers_coverage.py, test_stages_coverage.py, test_sql_table_extractor_coverage.py, test_banner_renderer_deadcode.py
2026-06-16 00:12:49 +03:00

655 lines
23 KiB
Python

# #region Test.AppDependencies [C:3] [TYPE Module] [SEMANTICS test,dependencies,fastapi,di,auth]
# @BRIEF Unit tests for src/dependencies.py — DI functions, auth dependencies, feature flags.
# @RELATION BINDS_TO -> [AppDependencies]
# @TEST_EDGE: config_manager_lazy_init -> get_config_manager initializes on first call
# @TEST_EDGE: plugin_loader_lazy_init -> get_plugin_loader initializes on first call
# @TEST_EDGE: feature_disabled -> require_feature raises 404
# @TEST_EDGE: api_key_expired -> get_api_key_principal raises 401
# @TEST_EDGE: missing_auth -> require_api_key_or_jwt raises 401 when no auth provided
import sys
from pathlib import Path
from unittest.mock import MagicMock, patch, AsyncMock
import pytest
from fastapi import HTTPException, Request, status
from fastapi.security import OAuth2PasswordBearer
from jose import JWTError
_src = str(Path(__file__).resolve().parent.parent / "src")
if _src not in sys.path:
sys.path.insert(0, _src)
# ── get_config_manager ──
class TestGetConfigManager:
"""DI: get_config_manager() lazy init + reuse."""
def test_get_config_manager_initializes(self):
"""Lazy init: creates ConfigManager on first call."""
from src import dependencies
dependencies.config_manager = None
with patch('src.dependencies.init_db'), \
patch('src.dependencies.ConfigManager') as MockCM:
instance = MagicMock()
MockCM.return_value = instance
result = dependencies.get_config_manager()
assert result is instance
MockCM.assert_called_once()
def test_get_config_manager_reuses(self):
"""Reuse: returns existing instance on subsequent calls."""
from src import dependencies
mock_cm = MagicMock()
dependencies.config_manager = mock_cm
result = dependencies.get_config_manager()
assert result is mock_cm
def test_get_config_manager_after_init(self):
"""Clearing global causes re-init."""
from src import dependencies
dependencies.config_manager = None
with patch('src.dependencies.init_db'), \
patch('src.dependencies.ConfigManager') as MockCM:
instance = MagicMock()
MockCM.return_value = instance
result = dependencies.get_config_manager()
assert result is instance
# ── require_feature ──
class TestRequireFeature:
"""DI: require_feature factory."""
def test_feature_enabled(self):
"""Enabled feature passes."""
from src.dependencies import require_feature
mock_cm = MagicMock()
mock_cm.get_config.return_value.settings.features.test_feature = True
checker = require_feature("test_feature")
# Should not raise
checker(mock_cm)
def test_feature_disabled_raises_404(self):
"""Disabled feature raises HTTPException 404."""
from src.dependencies import require_feature
mock_cm = MagicMock()
mock_cm.get_config.return_value.settings.features.test_feature = False
checker = require_feature("test_feature")
with pytest.raises(HTTPException) as exc:
checker(mock_cm)
assert exc.value.status_code == status.HTTP_404_NOT_FOUND
assert "disabled" in exc.value.detail
# ── get_plugin_loader ──
class TestGetPluginLoader:
"""DI: get_plugin_loader() lazy init."""
def test_get_plugin_loader_initializes(self):
"""Lazy init: creates PluginLoader on first call."""
from src import dependencies
dependencies.plugin_loader = None
with patch('src.dependencies.PluginLoader') as MockPL, \
patch('src.dependencies.logger'):
instance = MagicMock()
MockPL.return_value = instance
instance.get_all_plugin_configs.return_value = []
result = dependencies.get_plugin_loader()
assert result is instance
def test_get_plugin_loader_reuses(self):
"""Reuse: returns existing instance."""
from src import dependencies
mock_pl = MagicMock()
dependencies.plugin_loader = mock_pl
result = dependencies.get_plugin_loader()
assert result is mock_pl
# ── get_task_manager ──
class TestGetTaskManager:
"""DI: get_task_manager() lazy init."""
def test_get_task_manager_initializes(self):
"""Lazy init: creates TaskManager on first call."""
from src import dependencies
dependencies.task_manager = None
with patch('src.dependencies.get_plugin_loader'), \
patch('src.dependencies.TaskManager') as MockTM, \
patch('src.dependencies.logger'):
instance = MagicMock()
MockTM.return_value = instance
result = dependencies.get_task_manager()
assert result is instance
def test_get_task_manager_reuses(self):
"""Reuse: returns existing instance."""
from src import dependencies
mock_tm = MagicMock()
dependencies.task_manager = mock_tm
result = dependencies.get_task_manager()
assert result is mock_tm
# ── get_scheduler_service ──
class TestGetSchedulerService:
"""DI: get_scheduler_service() lazy init."""
def test_get_scheduler_service_initializes(self):
"""Lazy init: creates SchedulerService on first call."""
from src import dependencies
dependencies.scheduler_service = None
with patch('src.dependencies.get_task_manager'), \
patch('src.dependencies.get_config_manager'), \
patch('src.dependencies.SchedulerService') as MockSS, \
patch('src.dependencies.logger'):
instance = MagicMock()
MockSS.return_value = instance
result = dependencies.get_scheduler_service()
assert result is instance
def test_get_scheduler_service_reuses(self):
"""Reuse: returns existing instance."""
from src import dependencies
mock_ss = MagicMock()
dependencies.scheduler_service = mock_ss
result = dependencies.get_scheduler_service()
assert result is mock_ss
# ── get_resource_service ──
class TestGetResourceService:
"""DI: get_resource_service() lazy init."""
def test_get_resource_service_initializes(self):
"""Lazy init: creates ResourceService on first call."""
from src import dependencies
dependencies.resource_service = None
with patch('src.dependencies.ResourceService') as MockRS, \
patch('src.dependencies.logger'):
instance = MagicMock()
MockRS.return_value = instance
result = dependencies.get_resource_service()
assert result is instance
def test_get_resource_service_reuses(self):
"""Reuse: returns existing instance."""
from src import dependencies
mock_rs = MagicMock()
dependencies.resource_service = mock_rs
result = dependencies.get_resource_service()
assert result is mock_rs
# ── get_mapping_service ──
class TestGetMappingService:
"""DI: get_mapping_service() creates new instance each time."""
def test_get_mapping_service(self):
"""Returns new MappingService with config_manager."""
from src.dependencies import get_mapping_service
mock_cm = MagicMock()
with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
patch('src.dependencies.MappingService') as MockMS:
instance = MagicMock()
MockMS.return_value = instance
result = get_mapping_service()
assert result is instance
MockMS.assert_called_once_with(mock_cm)
# ── get_clean_release_repository ──
class TestGetCleanReleaseRepository:
"""DI: get_clean_release_repository() returns shared singleton."""
def test_get_clean_release_repository(self):
"""Returns the module-level singleton."""
from src.dependencies import get_clean_release_repository
result = get_clean_release_repository()
assert result is not None
def test_singleton_identity(self):
"""Multiple calls return same instance."""
from src.dependencies import get_clean_release_repository
r1 = get_clean_release_repository()
r2 = get_clean_release_repository()
assert r1 is r2
# ── get_clean_release_facade ──
class TestGetCleanReleaseFacade:
"""DI: get_clean_release_facade() builds facade with fresh DB session."""
def test_get_clean_release_facade(self):
"""Creates CleanReleaseFacade with all repos."""
from src.dependencies import get_clean_release_facade
mock_db = MagicMock()
mock_cm = MagicMock()
with patch('src.dependencies.get_config_manager', return_value=mock_cm), \
patch('src.dependencies.CleanReleaseFacade') as MockFacade:
instance = MagicMock()
MockFacade.return_value = instance
result = get_clean_release_facade(mock_db)
assert result is instance
# Verify all repos passed to facade
call_kwargs = MockFacade.call_args[1]
assert "candidate_repo" in call_kwargs
assert "artifact_repo" in call_kwargs
assert "manifest_repo" in call_kwargs
assert "audit_repo" in call_kwargs
# ── get_current_user ──
class TestGetCurrentUser:
"""DI: get_current_user extracts user from JWT."""
def test_get_current_user_success(self):
"""Valid JWT returns User."""
from src.dependencies import get_current_user
mock_user = MagicMock()
mock_user.username = "testuser"
mock_repo = MagicMock()
mock_repo.get_user_by_username.return_value = mock_user
with patch('src.dependencies.decode_token', return_value={"sub": "testuser"}), \
patch('src.dependencies.is_token_blacklisted', return_value=False), \
patch('src.dependencies.AuthRepository', return_value=mock_repo):
result = get_current_user("valid_token", MagicMock())
assert result is mock_user
def test_get_current_user_invalid_token(self):
"""Invalid JWT raises 401."""
from src.dependencies import get_current_user
with patch('src.dependencies.decode_token', side_effect=JWTError("bad token")):
with pytest.raises(HTTPException) as exc:
get_current_user("bad_token", MagicMock())
assert exc.value.status_code == 401
def test_get_current_user_no_sub(self):
"""Token without sub raises 401."""
from src.dependencies import get_current_user
with patch('src.dependencies.decode_token', return_value={"sub": None}):
with pytest.raises(HTTPException) as exc:
get_current_user("token", MagicMock())
assert exc.value.status_code == 401
def test_get_current_user_blacklisted(self):
"""Blacklisted token raises 401."""
from src.dependencies import get_current_user
with patch('src.dependencies.decode_token', return_value={"sub": "user"}), \
patch('src.dependencies.is_token_blacklisted', return_value=True):
with pytest.raises(HTTPException) as exc:
get_current_user("revoked", MagicMock())
assert exc.value.status_code == 401
def test_get_current_user_not_found(self):
"""User not in DB raises 401."""
from src.dependencies import get_current_user
mock_repo = MagicMock()
mock_repo.get_user_by_username.return_value = None
with patch('src.dependencies.decode_token', return_value={"sub": "nobody"}), \
patch('src.dependencies.is_token_blacklisted', return_value=False), \
patch('src.dependencies.AuthRepository', return_value=mock_repo):
with pytest.raises(HTTPException) as exc:
get_current_user("token", MagicMock())
assert exc.value.status_code == 401
# ── has_permission ──
class TestHasPermission:
"""DI: has_permission factory."""
def _make_user(self, is_admin=False, permissions=None):
"""Build a mock User with roles and permissions."""
from src.models.auth import User, Role, Permission
user = MagicMock(spec=User)
if is_admin:
role = MagicMock(spec=Role)
role.name = "Admin"
role.is_admin = True
role.permissions = []
user.roles = [role]
else:
perms = []
for res, act in (permissions or []):
p = MagicMock(spec=Permission)
p.resource = res
p.action = act
perms.append(p)
role = MagicMock(spec=Role)
role.name = "User"
role.is_admin = False
role.permissions = perms
user.roles = [role]
return user
def test_has_permission_admin(self):
"""Admin user has all permissions."""
from src.dependencies import has_permission
checker = has_permission("dataset:session", "MANAGE")
user = self._make_user(is_admin=True)
result = checker(user)
assert result is user
def test_has_permission_granted(self):
"""User with matching permission passes."""
from src.dependencies import has_permission
checker = has_permission("dataset:session", "READ")
user = self._make_user(permissions=[("dataset:session", "READ")])
result = checker(user)
assert result is user
def test_has_permission_denied(self):
"""User without permission raises 403."""
from src.dependencies import has_permission
checker = has_permission("dataset:session", "DELETE")
user = self._make_user(permissions=[("dataset:session", "READ")])
with patch('src.core.auth.logger.log_security_event'):
with pytest.raises(HTTPException) as exc:
checker(user)
assert exc.value.status_code == 403
# ── get_api_key_principal ──
class TestGetApiKeyPrincipal:
"""DI: get_api_key_principal extracts principal from API key header."""
@pytest.mark.asyncio
async def test_no_header_returns_none(self):
"""No X-API-Key header returns None."""
from src.dependencies import get_api_key_principal
request = MagicMock(spec=Request)
request.headers.get.return_value = None
result = await get_api_key_principal(request, MagicMock())
assert result is None
@pytest.mark.asyncio
async def test_valid_key(self):
"""Valid API key returns APIKeyPrincipal."""
from src.dependencies import get_api_key_principal
request = MagicMock(spec=Request)
request.headers.get.return_value = "valid-key-123"
mock_api_key = MagicMock()
mock_api_key.id = "key-1"
mock_api_key.name = "Test Key"
mock_api_key.prefix = "prefix"
mock_api_key.active = True
mock_api_key.expires_at = None
mock_api_key.environment_id = "env-1"
mock_api_key.permissions = ["dataset:read"]
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_api_key
with patch('src.core.auth.api_key.hash_api_key', return_value="hashed_key"):
result = await get_api_key_principal(request, mock_db)
assert result is not None
assert result.name == "Test Key"
assert result.api_key_id == "key-1"
assert result.environment_id == "env-1"
assert "dataset:read" in result.permissions
@pytest.mark.asyncio
async def test_invalid_key_raises(self):
"""Invalid API key raises 401."""
from src.dependencies import get_api_key_principal
request = MagicMock(spec=Request)
request.headers.get.return_value = "bad-key"
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
with patch('src.core.auth.api_key.hash_api_key', return_value="bad_hash"):
with pytest.raises(HTTPException) as exc:
await get_api_key_principal(request, mock_db)
assert exc.value.status_code == 401
@pytest.mark.asyncio
async def test_revoked_key_raises(self):
"""Revoked API key raises 401."""
from src.dependencies import get_api_key_principal
request = MagicMock(spec=Request)
request.headers.get.return_value = "revoked-key"
mock_api_key = MagicMock()
mock_api_key.active = False
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_api_key
with patch('src.core.auth.api_key.hash_api_key', return_value="hash"):
with pytest.raises(HTTPException) as exc:
await get_api_key_principal(request, mock_db)
assert exc.value.status_code == 401
assert "revoked" in exc.value.detail.lower()
@pytest.mark.asyncio
async def test_expired_key_raises(self):
"""Expired API key raises 401."""
from datetime import UTC, datetime, timedelta
from src.dependencies import get_api_key_principal
request = MagicMock(spec=Request)
request.headers.get.return_value = "expired-key"
mock_api_key = MagicMock()
mock_api_key.active = True
mock_api_key.expires_at = datetime.now(UTC) - timedelta(hours=1)
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_api_key
with patch('src.core.auth.api_key.hash_api_key', return_value="hash"):
with pytest.raises(HTTPException) as exc:
await get_api_key_principal(request, mock_db)
assert exc.value.status_code == 401
assert "expired" in exc.value.detail.lower()
@pytest.mark.asyncio
async def test_expired_key_no_tz_raises(self):
"""Expired API key without timezone raises 401."""
from datetime import datetime
from src.dependencies import get_api_key_principal
request = MagicMock(spec=Request)
request.headers.get.return_value = "expired-key"
mock_api_key = MagicMock()
mock_api_key.active = True
mock_api_key.expires_at = datetime(2020, 1, 1, 0, 0, 0) # naive, clearly past
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_api_key
with patch('src.core.auth.api_key.hash_api_key', return_value="hash"):
with pytest.raises(HTTPException) as exc:
await get_api_key_principal(request, mock_db)
assert exc.value.status_code == 401
# ── check_api_key_environment_scope ──
class TestCheckApiKeyEnvironmentScope:
"""check_api_key_environment_scope enforces key scope."""
@pytest.mark.asyncio
async def test_no_key_header_returns(self):
"""No API key header → no check."""
from src.dependencies import check_api_key_environment_scope
request = MagicMock(spec=Request)
request.headers.get.return_value = None
# Should not raise
await check_api_key_environment_scope(request, MagicMock(), "env-1")
@pytest.mark.asyncio
async def test_key_scope_matches(self):
"""Key scope matches target → pass."""
from src.dependencies import check_api_key_environment_scope
request = MagicMock(spec=Request)
request.headers.get.return_value = "valid-key"
mock_api_key = MagicMock()
mock_api_key.environment_id = "env-1"
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_api_key
with patch('src.core.auth.api_key.hash_api_key', return_value="hash"):
await check_api_key_environment_scope(request, mock_db, "env-1")
@pytest.mark.asyncio
async def test_key_scope_mismatch_raises(self):
"""Key scope differs from target → 400."""
from src.dependencies import check_api_key_environment_scope
request = MagicMock(spec=Request)
request.headers.get.return_value = "wrong-key"
mock_api_key = MagicMock()
mock_api_key.environment_id = "env-1"
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = mock_api_key
with patch('src.core.auth.api_key.hash_api_key', return_value="hash"):
with pytest.raises(HTTPException) as exc:
await check_api_key_environment_scope(request, mock_db, "env-2")
assert exc.value.status_code == 400
assert "restricted" in exc.value.detail.lower()
@pytest.mark.asyncio
async def test_key_not_found_returns(self):
"""Key not in DB → pass (auth handled elsewhere)."""
from src.dependencies import check_api_key_environment_scope
request = MagicMock(spec=Request)
request.headers.get.return_value = "unknown-key"
mock_db = MagicMock()
mock_db.query.return_value.filter.return_value.first.return_value = None
with patch('src.core.auth.api_key.hash_api_key', return_value="hash"):
await check_api_key_environment_scope(request, mock_db, "env-1")
# ── require_api_key_or_jwt (partial — JWT path) ──
class TestRequireApiKeyOrJwt:
"""require_api_key_or_jwt factory — JWT path testing."""
@pytest.mark.asyncio
async def test_jwt_success_admin(self):
"""JWT with Admin role passes."""
from src.dependencies import require_api_key_or_jwt
mock_user = MagicMock()
mock_user.username = "admin"
role = MagicMock()
role.name = "Admin"
role.permissions = []
mock_user.roles = [role]
mock_repo = MagicMock()
mock_repo.get_user_by_username.return_value = mock_user
# Build the dependency
dep = require_api_key_or_jwt(
required_api_permission="dataset:read",
required_jwt_resource="dataset",
required_jwt_action="read",
environment_id="env-1",
)
mock_request = MagicMock(spec=Request)
mock_request.json = AsyncMock(return_value={})
with patch('src.dependencies.decode_token', return_value={"sub": "admin"}), \
patch('src.dependencies.AuthRepository', return_value=mock_repo), \
patch('src.core.auth.api_key.hash_api_key'):
result = await dep(mock_request, MagicMock(), MagicMock(), "valid_token")
assert result == "jwt:admin"
@pytest.mark.asyncio
async def test_no_auth_raises_401(self):
"""Neither JWT nor API key → 401."""
from src.dependencies import require_api_key_or_jwt
dep = require_api_key_or_jwt(
required_api_permission="test",
required_jwt_resource="test",
required_jwt_action="test",
)
mock_request = MagicMock(spec=Request)
mock_request.headers.get.return_value = None
mock_request.json = AsyncMock(return_value={})
with pytest.raises(HTTPException) as exc:
await dep(mock_request, MagicMock(), MagicMock(), None)
assert exc.value.status_code == 401
assert "Authentication required" in exc.value.detail