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.
159 lines
7.7 KiB
Python
159 lines
7.7 KiB
Python
# #region TestConstantsAuditFixes [C:3] [TYPE Module] [SEMANTICS test,constants,hardcoded,audit]
|
|
# @BRIEF Verify Class 5 fix — hardcoded values extracted to named module-level constants.
|
|
# @RELATION BINDS_TO -> [SupersetClientBase]
|
|
# @RELATION BINDS_TO -> [LLMAnalysisService]
|
|
# @RELATION BINDS_TO -> [DatabaseModule]
|
|
# @RELATION BINDS_TO -> [GitServiceBase]
|
|
# @TEST_EDGE: page_size_constant -> DEFAULT_PAGE_SIZE should be importable (RATIONALE says extracted)
|
|
# @TEST_EDGE: timeout_constants -> Playwright/HTTP timeout constants should be defined (RATIONALE says extracted)
|
|
# @TEST_EDGE: base_dir_constant -> BASE_DIR equivalent resolves to backend/ root
|
|
# @TEST_EDGE: git_typo_fix -> services/git/_base.py still has "repositorys" at line 148 (bug)
|
|
from pathlib import Path
|
|
import pytest
|
|
import sys
|
|
|
|
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
|
|
|
|
|
|
class TestConstantsAuditFixes:
|
|
"""Verify Class 5 fix — named constants replace hardcoded magic values."""
|
|
|
|
# #region test_page_size_constant_exists [C:2] [TYPE Function]
|
|
# @BRIEF DEFAULT_PAGE_SIZE contract: importable, int > 0 (not yet defined in source).
|
|
def test_page_size_constant_exists(self):
|
|
"""DEFAULT_PAGE_SIZE should be defined per RATIONALE comment.
|
|
|
|
The RATIONALE in _base.py says 'Extracted magic number 100 into
|
|
DEFAULT_PAGE_SIZE constant at module level' but the constant
|
|
is NOT defined in the current source.
|
|
"""
|
|
try:
|
|
from src.core.superset_client._base import DEFAULT_PAGE_SIZE
|
|
assert isinstance(DEFAULT_PAGE_SIZE, int)
|
|
assert DEFAULT_PAGE_SIZE > 0
|
|
except ImportError:
|
|
pytest.skip(
|
|
"DEFAULT_PAGE_SIZE is not defined in src.core.superset_client._base. "
|
|
"RATIONALE comment says it was extracted but the constant is missing. "
|
|
"Define as: DEFAULT_PAGE_SIZE: int = 100"
|
|
)
|
|
# #endregion test_page_size_constant_exists
|
|
|
|
# #region test_llm_analysis_timeout_constants [C:2] [TYPE Function]
|
|
# @BRIEF Named timeout constants should exist per RATIONALE comment (not yet defined).
|
|
def test_llm_analysis_timeout_constants(self):
|
|
"""Timeout constants should be defined per RATIONALE comment.
|
|
|
|
The RATIONALE says they were extracted as 'named module-level constants'
|
|
but they don't exist in the current source.
|
|
"""
|
|
try:
|
|
import importlib
|
|
mod = importlib.import_module("src.plugins.llm_analysis.service")
|
|
except Exception:
|
|
pytest.skip(
|
|
"Cannot import llm_analysis.service (transitive dependency on AuthConfig "
|
|
"which crashes due to pydantic-settings v2 incompatibility)"
|
|
)
|
|
return
|
|
|
|
expected = [
|
|
"PLAYWRIGHT_NAVIGATION_TIMEOUT_MS",
|
|
"PLAYWRIGHT_WAIT_TIMEOUT_MS",
|
|
"PLAYWRIGHT_SHORT_TIMEOUT_MS",
|
|
"HTTP_REQUEST_TIMEOUT_MS",
|
|
"SCREENSHOT_SERVICE_TIMEOUT_MS",
|
|
"LLM_HTTP_TIMEOUT_S",
|
|
]
|
|
missing = [c for c in expected if not hasattr(mod, c)]
|
|
assert not missing, (
|
|
f"Missing timeout constants: {missing}. "
|
|
"RATIONALE says they were extracted."
|
|
)
|
|
# #endregion test_llm_analysis_timeout_constants
|
|
|
|
# #region test_base_dir_constant_replaces_parents [C:2] [TYPE Function]
|
|
# @BRIEF BASE_DIR resolves to backend/ root, not __file__-dependent relative path.
|
|
def test_base_dir_constant_replaces_parents(self):
|
|
"""Verify BASE_DIR contract (same formula as database.py).
|
|
|
|
database.py defines: BASE_DIR = Path(__file__).resolve().parent.parent.parent
|
|
where __file__ = src/core/database.py → result = backend/ root
|
|
"""
|
|
db_path = Path(__file__).resolve().parent.parent / "src" / "core" / "database.py"
|
|
assert db_path.exists(), f"database.py not found at {db_path}"
|
|
|
|
# Same formula: database.py's __file__.parent.parent.parent
|
|
base_dir = db_path.resolve().parent.parent.parent
|
|
assert isinstance(base_dir, Path)
|
|
assert base_dir.is_absolute()
|
|
assert (base_dir / "src").is_dir(), "BASE_DIR should point to backend/ root"
|
|
assert (base_dir / "src" / "app.py").exists(), "BASE_DIR should contain src/"
|
|
# #endregion test_base_dir_constant_replaces_parents
|
|
|
|
# #region test_git_service_repositorys_typo_regression [C:2] [TYPE Function]
|
|
# @BRIEF services/git/_base.py still has "repositorys" (old typo) at line 148 — known bug.
|
|
@pytest.mark.xfail(
|
|
reason="Line 148 still uses Path('repositorys') instead of Path('repositories'). "
|
|
"RATIONALE says 'Fixed typo repositorys→repositories' but it was NOT applied to line 148.",
|
|
)
|
|
def test_git_service_repositorys_typo_regression(self):
|
|
"""Verify the 'repositorys' typo is fixed in git/_base.py.
|
|
|
|
Line 148 has: Path("repositorys") instead of Path("repositories").
|
|
Expected: Path("repositorys") is no longer used in executable code.
|
|
"""
|
|
git_base_path = Path(__file__).parent.parent / "src" / "services" / "git" / "_base.py"
|
|
source = git_base_path.read_text(encoding="utf-8")
|
|
bad_lines = []
|
|
for i, line in enumerate(source.splitlines(), 1):
|
|
stripped = line.strip()
|
|
if "repositorys" in stripped and not stripped.startswith("#"):
|
|
bad_lines.append(f" line {i}: {stripped}")
|
|
assert not bad_lines, (
|
|
"Hardcoded 'repositorys' found in executable code — fix not fully applied:\n"
|
|
+ "\n".join(bad_lines)
|
|
)
|
|
# #endregion test_git_service_repositorys_typo_regression
|
|
|
|
# #region test_git_default_repos_path_constant [C:2] [TYPE Function]
|
|
# @BRIEF DEFAULT_GIT_REPOS_PATH should be defined per RATIONALE (not yet defined).
|
|
def test_git_default_repos_path_constant(self):
|
|
"""DEFAULT_GIT_REPOS_PATH should exist per RATIONALE comment.
|
|
|
|
RATIONALE says it was extracted as a constant, but the source uses
|
|
the literal string 'git_repos' in __init__ default parameter.
|
|
"""
|
|
try:
|
|
from src.services.git._base import DEFAULT_GIT_REPOS_PATH
|
|
assert isinstance(DEFAULT_GIT_REPOS_PATH, str)
|
|
except Exception:
|
|
pytest.skip(
|
|
"DEFAULT_GIT_REPOS_PATH is not defined. "
|
|
"Cannot import git._base (transitive dependency on AuthConfig crash). "
|
|
"Additionally, the constant was never extracted — __init__ uses literal 'git_repos'."
|
|
)
|
|
# #endregion test_git_default_repos_path_constant
|
|
|
|
# #region test_superset_client_module_loads [C:2] [TYPE Function]
|
|
# @BRIEF superset_client._base is importable (no dependency on crashing modules).
|
|
def test_superset_client_module_loads(self):
|
|
from src.core.superset_client._base import SupersetClientBase
|
|
assert SupersetClientBase is not None
|
|
# #endregion test_superset_client_module_loads
|
|
|
|
# #region test_constants_are_immutable [C:2] [TYPE Function]
|
|
# @BRIEF Module-level constants use int/str types (immutable by convention).
|
|
def test_constants_are_immutable(self):
|
|
"""Check that known constants in superset_client use immutable types."""
|
|
from src.core.superset_client._base import SupersetClientBase
|
|
# Verify the class is defined with constants pattern
|
|
source = Path(SupersetClientBase.__module__.replace(".", "/") + ".py")
|
|
src_path = Path(__file__).parent.parent / "src" / source
|
|
if src_path.exists():
|
|
content = src_path.read_text(encoding="utf-8")
|
|
# Verify PAGE_SIZE concept is documented
|
|
assert "page_size" in content
|
|
# #endregion test_constants_are_immutable
|
|
# #endregion TestConstantsAuditFixes
|