Complete testing system: pyproject.toml (pytest markers), test.sh orchestrator with auto app start/stop and colorful summary, pre-push hook, Gitea Actions workflow. New QA tests: API health (7 endpoints), responsive (3 viewports), log monitoring (ERROR/ORA-/Traceback detection), real GoMag sync, PL/SQL package validation, smoke prod (read-only). Converted test_app_basic.py and test_integration.py to pytest. Added pytestmark to all existing tests (unit/e2e/oracle). E2E conftest upgraded: console error collector, screenshot on failure, auto-detect live app on :5003. Usage: ./test.sh ci (30s) | ./test.sh full (2-3min) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
115 lines
3.4 KiB
Python
115 lines
3.4 KiB
Python
"""
|
|
Test: Basic App Import and Route Tests (pytest-compatible)
|
|
==========================================================
|
|
Tests module imports and all GET routes without requiring Oracle.
|
|
Converted from api/test_app_basic.py.
|
|
|
|
Run:
|
|
pytest api/tests/test_app_basic.py -v
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
|
|
import pytest
|
|
|
|
# --- Marker: all tests here are unit (no Oracle) ---
|
|
pytestmark = pytest.mark.unit
|
|
|
|
# --- Set env vars BEFORE any app import ---
|
|
_tmpdir = tempfile.mkdtemp()
|
|
_sqlite_path = os.path.join(_tmpdir, "test_import.db")
|
|
|
|
os.environ["FORCE_THIN_MODE"] = "true"
|
|
os.environ["SQLITE_DB_PATH"] = _sqlite_path
|
|
os.environ["ORACLE_DSN"] = "dummy"
|
|
os.environ["ORACLE_USER"] = "dummy"
|
|
os.environ["ORACLE_PASSWORD"] = "dummy"
|
|
os.environ.setdefault("JSON_OUTPUT_DIR", _tmpdir)
|
|
|
|
# Add api/ to path so we can import app
|
|
_api_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
if _api_dir not in sys.path:
|
|
sys.path.insert(0, _api_dir)
|
|
|
|
|
|
# -------------------------------------------------------
|
|
# Section 1: Module Import Checks
|
|
# -------------------------------------------------------
|
|
|
|
MODULES = [
|
|
"app.config",
|
|
"app.database",
|
|
"app.main",
|
|
"app.routers.health",
|
|
"app.routers.dashboard",
|
|
"app.routers.mappings",
|
|
"app.routers.sync",
|
|
"app.routers.validation",
|
|
"app.routers.articles",
|
|
"app.services.sqlite_service",
|
|
"app.services.scheduler_service",
|
|
"app.services.mapping_service",
|
|
"app.services.article_service",
|
|
"app.services.validation_service",
|
|
"app.services.import_service",
|
|
"app.services.sync_service",
|
|
"app.services.order_reader",
|
|
]
|
|
|
|
|
|
@pytest.mark.parametrize("module_name", MODULES)
|
|
def test_module_import(module_name):
|
|
"""Each app module should import without errors."""
|
|
__import__(module_name)
|
|
|
|
|
|
# -------------------------------------------------------
|
|
# Section 2: Route Tests via TestClient
|
|
# -------------------------------------------------------
|
|
|
|
# (path, expected_status_codes, is_known_oracle_failure)
|
|
GET_ROUTES = [
|
|
("/health", [200], False),
|
|
("/", [200, 500], False),
|
|
("/missing-skus", [200, 500], False),
|
|
("/mappings", [200, 500], False),
|
|
("/logs", [200, 500], False),
|
|
("/api/mappings", [200, 503], True),
|
|
("/api/mappings/export-csv", [200, 503], True),
|
|
("/api/mappings/csv-template", [200], False),
|
|
("/api/sync/status", [200], False),
|
|
("/api/sync/history", [200], False),
|
|
("/api/sync/schedule", [200], False),
|
|
("/api/validate/missing-skus", [200], False),
|
|
("/api/validate/missing-skus?page=1&per_page=10", [200], False),
|
|
("/api/sync/run/nonexistent/log", [200, 404], False),
|
|
("/api/articles/search?q=ab", [200, 503], True),
|
|
("/settings", [200, 500], False),
|
|
]
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def client():
|
|
"""Create a TestClient with lifespan for all route tests."""
|
|
from fastapi.testclient import TestClient
|
|
from app.main import app
|
|
|
|
with TestClient(app, raise_server_exceptions=False) as c:
|
|
yield c
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"path,expected_codes,is_oracle_route",
|
|
GET_ROUTES,
|
|
ids=[p for p, _, _ in GET_ROUTES],
|
|
)
|
|
def test_route(client, path, expected_codes, is_oracle_route):
|
|
"""Each GET route should return an expected status code."""
|
|
resp = client.get(path)
|
|
assert resp.status_code in expected_codes, (
|
|
f"GET {path} returned {resp.status_code}, expected one of {expected_codes}. "
|
|
f"Body: {resp.text[:300]}"
|
|
)
|