3.0 KiB
Backend Test Import Patterns
Problem
The ss-tools backend uses relative imports inside packages (e.g., from ...models.task import TaskRecord in persistence.py). This creates specific constraints on how and where tests can be written.
Key Rules
1. Packages with __init__.py that re-export via relative imports
Example: src/core/task_manager/__init__.py imports .manager → .persistence → from ...models.task (3-level relative import).
Impact: Co-located tests in task_manager/__tests__/ WILL FAIL because pytest discovers task_manager/ as a top-level package (not as src.core.task_manager), and the 3-level from ... goes beyond the top-level.
Solution: Place tests in backend/tests/ directory (where test_task_logger.py already lives). Import using from src.core.task_manager.XXX import ... which works because backend/ is the pytest rootdir.
2. Packages WITHOUT __init__.py:
Example: src/core/auth/ has NO __init__.py.
Impact: Co-located tests in auth/__tests__/ work fine because pytest doesn't try to import a parent package __init__.py.
3. Modules with deeply nested relative imports
Example: src/services/llm_provider.py uses from ..models.llm import LLMProvider and from ..plugins.llm_analysis.models import LLMProviderConfig.
Impact: Direct import (from src.services.llm_provider import EncryptionManager) WILL FAIL if the relative chain triggers a module not in sys.path or if it tries to import beyond root.
Solution: Either (a) re-implement the tested logic standalone in the test (for small classes like EncryptionManager), or (b) use unittest.mock.patch to mock the problematic imports before importing the module.
Working Test Locations
| Package | __init__.py? |
Relative imports? | Co-located OK? | Test location |
|---|---|---|---|---|
core/task_manager/ |
YES | from ...models.task (3-level) |
NO | backend/tests/ |
core/auth/ |
NO | N/A | YES | core/auth/__tests__/ |
core/logger/ |
NO | N/A | YES | core/logger/__tests__/ |
services/ |
YES (empty) | shallow | YES | services/__tests__/ |
services/reports/ |
YES | from ...core.logger |
NO (most likely) | backend/tests/ or mock |
models/ |
YES | shallow | YES | models/__tests__/ |
Safe Import Patterns for Tests
# In backend/tests/test_*.py:
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
# Then import:
from src.core.task_manager.models import Task, TaskStatus
from src.core.task_manager.persistence import TaskPersistenceService
from src.models.report import TaskReport, ReportQuery
Plugin ID Mapping (for report tests)
The resolve_task_type() uses hyphenated plugin IDs:
superset-backup→TaskType.BACKUPsuperset-migration→TaskType.MIGRATIONllm_dashboard_validation→TaskType.LLM_VERIFICATIONdocumentation→TaskType.DOCUMENTATION- anything else →
TaskType.UNKNOWN