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>
154 lines
5.0 KiB
Python
154 lines
5.0 KiB
Python
"""
|
|
Oracle Integration Tests for GoMag Import Manager (pytest-compatible)
|
|
=====================================================================
|
|
Requires Oracle connectivity and valid .env configuration.
|
|
Converted from api/test_integration.py.
|
|
|
|
Run:
|
|
pytest api/tests/test_integration.py -v
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
# --- Marker: all tests require Oracle ---
|
|
pytestmark = pytest.mark.oracle
|
|
|
|
# Set working directory to project root so relative paths in .env work
|
|
_script_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
|
|
_project_root = os.path.dirname(_script_dir)
|
|
|
|
# Load .env from api/ before importing app modules
|
|
from dotenv import load_dotenv
|
|
|
|
_env_path = os.path.join(_script_dir, ".env")
|
|
load_dotenv(_env_path, override=True)
|
|
|
|
# Add api/ to path so app package is importable
|
|
if _script_dir not in sys.path:
|
|
sys.path.insert(0, _script_dir)
|
|
|
|
|
|
@pytest.fixture(scope="module")
|
|
def client():
|
|
"""Create a TestClient with Oracle lifespan."""
|
|
from fastapi.testclient import TestClient
|
|
from app.main import app
|
|
|
|
with TestClient(app) as c:
|
|
yield c
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test A: GET /health — Oracle must show as connected
|
|
# ---------------------------------------------------------------------------
|
|
def test_health_oracle_connected(client):
|
|
resp = client.get("/health")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body.get("oracle") == "ok", f"oracle={body.get('oracle')!r}"
|
|
assert body.get("sqlite") == "ok", f"sqlite={body.get('sqlite')!r}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test B: Mappings CRUD cycle
|
|
# ---------------------------------------------------------------------------
|
|
TEST_SKU = "PYTEST_INTEG_SKU_001"
|
|
TEST_CODMAT = "PYTEST_CODMAT_001"
|
|
|
|
|
|
def test_mappings_create(client):
|
|
resp = client.post("/api/mappings", json={
|
|
"sku": TEST_SKU,
|
|
"codmat": TEST_CODMAT,
|
|
"cantitate_roa": 2.5,
|
|
})
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body.get("success") is True, f"create returned: {body}"
|
|
|
|
|
|
def test_mappings_list_after_create(client):
|
|
resp = client.get("/api/mappings", params={"search": TEST_SKU})
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
mappings = body.get("mappings", [])
|
|
found = any(
|
|
m["sku"] == TEST_SKU and m["codmat"] == TEST_CODMAT
|
|
for m in mappings
|
|
)
|
|
assert found, f"mapping not found in list; got {mappings}"
|
|
|
|
|
|
def test_mappings_update(client):
|
|
resp = client.put(f"/api/mappings/{TEST_SKU}/{TEST_CODMAT}", json={
|
|
"cantitate_roa": 3.0,
|
|
})
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body.get("success") is True, f"update returned: {body}"
|
|
|
|
|
|
def test_mappings_delete(client):
|
|
resp = client.delete(f"/api/mappings/{TEST_SKU}/{TEST_CODMAT}")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert body.get("success") is True, f"delete returned: {body}"
|
|
|
|
|
|
def test_mappings_verify_soft_deleted(client):
|
|
resp = client.get("/api/mappings", params={"search": TEST_SKU})
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
mappings = body.get("mappings", [])
|
|
deleted = any(
|
|
m["sku"] == TEST_SKU and m["codmat"] == TEST_CODMAT and m.get("activ") == 0
|
|
for m in mappings
|
|
)
|
|
assert deleted, (
|
|
f"expected activ=0 for deleted mapping, got: "
|
|
f"{[m for m in mappings if m['sku'] == TEST_SKU]}"
|
|
)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test C: GET /api/articles/search
|
|
# ---------------------------------------------------------------------------
|
|
def test_articles_search(client):
|
|
search_terms = ["01", "A", "PH"]
|
|
found_results = False
|
|
for term in search_terms:
|
|
resp = client.get("/api/articles/search", params={"q": term})
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
results_list = body.get("results", [])
|
|
if results_list:
|
|
found_results = True
|
|
break
|
|
assert found_results, f"all search terms {search_terms} returned empty results"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test D: POST /api/validate/scan
|
|
# ---------------------------------------------------------------------------
|
|
def test_validate_scan(client):
|
|
resp = client.post("/api/validate/scan")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
has_shape = "json_files" in body and ("orders" in body or "total_orders" in body)
|
|
assert has_shape, f"unexpected response shape: {list(body.keys())}"
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Test E: GET /api/sync/history
|
|
# ---------------------------------------------------------------------------
|
|
def test_sync_history(client):
|
|
resp = client.get("/api/sync/history")
|
|
assert resp.status_code == 200
|
|
body = resp.json()
|
|
assert "runs" in body, f"missing 'runs' key; got keys: {list(body.keys())}"
|
|
assert isinstance(body["runs"], list)
|
|
assert "total" in body
|