Files
ss-tools/backend/tests/test_logging_audit_fixes.py
busya 4205618ee6 chore: eliminate all deprecation warnings from tests and linter
Warnings fixed:
- datetime.utcnow() → datetime.now(UTC) across 48+ files (src/ + tests/)
- datetime.utcnow (callback ref) → lambda: datetime.now(UTC) in model fields (18 files)
- Pydantic class Config → model_config = ConfigDict(...) (16 files)
- Pydantic .dict() → .model_dump() (8 files)
- ConfigDict(allow_population_by_field_name=True) → validate_by_name=True
- SQLAlchemy declarative_base() import path updated
- FastAPI on_event → lifespan context manager (app.py)
- Import sorting (ruff I001) auto-fixed across all files
- Fixed broken re-export chains that ruff F401 cleanup broke:
  _validate_bcp47: service.py now imports from dictionary_validation directly
  job_to_response: _job_routes.py and test imports from service_utils directly
  fetch_datasource_metadata: restored re-export in service.py
- Added missing TranslateJobService import in _job_routes.py (was deleted by F401)
- Added ConfigDict(protected_namespaces=()) for DashboardDatasetItem schema field
- pytest.ini: replaced deprecated importmode with asyncio_mode

All 440 tests pass with zero deprecation warnings.
2026-05-26 19:18:28 +03:00

116 lines
5.1 KiB
Python

# #region TestLoggingAuditFixes [C:3] [TYPE Module] [SEMANTICS test,logging,except,audit]
# @BRIEF Verify Class 2 fix — no bare `except: pass` patterns remain in src/.
# @RELATION BINDS_TO -> [EXT:frontend:PluginLoaderCore]
# @TEST_EDGE: except_pass_replaced -> bare `except: pass` patterns are absent from src/
# @TEST_EDGE: plugin_loader_uses_logger -> plugin_loader.py uses logger, not print
import ast
from pathlib import Path
import sys
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
class TestLoggingAuditFixes:
"""Verify that all bare `except: pass` patterns have been replaced with proper logging."""
PLUGIN_LOADER_PATH = Path(__file__).parent.parent / "src" / "core" / "plugin_loader.py"
# #region _collect_src_files [C:1] [TYPE Function]
# @BRIEF Collect all .py files under src/ excluding test/ and mock/ directories.
@staticmethod
def _collect_src_files():
src_root = Path(__file__).parent.parent / "src"
return sorted(
p for p in src_root.rglob("*.py")
if "test" not in p.parts and "mock" not in p.parts and p.name != "__init__.py"
)
# #endregion _collect_src_files
# #region test_except_pass_replaced_with_log [C:2] [TYPE Function]
# @BRIEF AST audit: bare `except: pass` nodes are absent from src/ source files.
def test_except_pass_replaced_with_log(self):
files_with_bare_except_pass = []
for filepath in self._collect_src_files():
try:
tree = ast.parse(filepath.read_text(encoding="utf-8"), filename=str(filepath))
except SyntaxError:
continue
for node in ast.walk(tree):
if not isinstance(node, ast.ExceptHandler):
continue
if node.type is not None:
continue
if len(node.body) == 1 and isinstance(node.body[0], ast.Pass):
files_with_bare_except_pass.append(str(filepath))
break
assert not files_with_bare_except_pass, (
f"Bare `except: pass` found in {len(files_with_bare_except_pass)} file(s):\n"
+ "\n".join(files_with_bare_except_pass)
)
# #endregion test_except_pass_replaced_with_log
# #region test_plugin_loader_uses_logger_not_print [C:2] [TYPE Function]
# @BRIEF plugin_loader.py (src/core/) uses logger for error/warning, not print().
def test_plugin_loader_uses_logger_not_print(self):
code = self.PLUGIN_LOADER_PATH.read_text(encoding="utf-8")
tree = ast.parse(code, filename=str(self.PLUGIN_LOADER_PATH))
# Find all print() calls in executable code
print_lines = []
for node in ast.walk(tree):
if (isinstance(node, ast.Call)
and isinstance(node.func, ast.Name)
and node.func.id == "print"):
print_lines.append(node.lineno)
assert len(print_lines) == 0, (
f"plugin_loader.py still contains print() calls at lines: {print_lines}"
)
# #endregion test_plugin_loader_uses_logger_not_print
# #region test_plugin_loader_imports_logger [C:2] [TYPE Function]
# @BRIEF plugin_loader.py imports a logger or _logger for structured reporting.
def test_plugin_loader_imports_logger(self):
code = self.PLUGIN_LOADER_PATH.read_text(encoding="utf-8")
tree = ast.parse(code, filename=str(self.PLUGIN_LOADER_PATH))
has_logger_import = False
for node in ast.walk(tree):
if isinstance(node, (ast.Import, ast.ImportFrom)):
for alias in node.names:
if "logger" in alias.name.lower():
has_logger_import = True
break
assert has_logger_import, (
"plugin_loader.py should import 'logger' or '_logger' "
"for log-based error handling (instead of print)"
)
# #endregion test_plugin_loader_imports_logger
# #region test_plugin_loader_logger_calls_exist [C:2] [TYPE Function]
# @BRIEF plugin_loader.py references logger.error/warning/info in its code.
def test_plugin_loader_logger_calls_exist(self):
code = self.PLUGIN_LOADER_PATH.read_text(encoding="utf-8")
tree = ast.parse(code, filename=str(self.PLUGIN_LOADER_PATH))
logger_calls = []
for node in ast.walk(tree):
if (isinstance(node, ast.Call)
and isinstance(node.func, ast.Attribute)
and isinstance(node.func.value, ast.Name)
and "logger" in node.func.value.id.lower()):
logger_calls.append((node.lineno, node.func.attr))
error_levels = {"error", "warning", "info", "debug", "reason", "reflect", "explore"}
matching = [call for call in logger_calls if call[1] in error_levels]
assert len(matching) >= 4, (
f"plugin_loader.py has only {len(matching)} logger.error/warning/info calls "
f"(expected at least 4 for the 4 replaced print() statements): {matching}"
)
# #endregion test_plugin_loader_logger_calls_exist
# #endregion TestLoggingAuditFixes