# 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 ```python # 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.BACKUP` - `superset-migration` → `TaskType.MIGRATION` - `llm_dashboard_validation` → `TaskType.LLM_VERIFICATION` - `documentation` → `TaskType.DOCUMENTATION` - anything else → `TaskType.UNKNOWN`