feat: add CI/CD testing infrastructure with test.sh orchestrator
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>
This commit is contained in:
87
api/tests/qa/test_qa_api_health.py
Normal file
87
api/tests/qa/test_qa_api_health.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""QA tests for API endpoint health and basic contract validation."""
|
||||
import time
|
||||
import urllib.request
|
||||
import pytest
|
||||
import httpx
|
||||
|
||||
pytestmark = pytest.mark.qa
|
||||
|
||||
ENDPOINTS = [
|
||||
"/health",
|
||||
"/api/dashboard/orders",
|
||||
"/api/sync/status",
|
||||
"/api/sync/history",
|
||||
"/api/validate/missing-skus",
|
||||
"/api/mappings",
|
||||
"/api/settings",
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def client(base_url):
|
||||
"""Create httpx client; skip all if app is not reachable."""
|
||||
try:
|
||||
urllib.request.urlopen(f"{base_url}/health", timeout=3)
|
||||
except Exception:
|
||||
pytest.skip(f"App not reachable at {base_url}")
|
||||
with httpx.Client(base_url=base_url, timeout=10.0) as c:
|
||||
yield c
|
||||
|
||||
|
||||
def test_health(client):
|
||||
r = client.get("/health")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "oracle" in data
|
||||
assert "sqlite" in data
|
||||
|
||||
|
||||
def test_dashboard_orders(client):
|
||||
r = client.get("/api/dashboard/orders")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "orders" in data
|
||||
assert "counts" in data
|
||||
|
||||
|
||||
def test_sync_status(client):
|
||||
r = client.get("/api/sync/status")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "status" in data
|
||||
|
||||
|
||||
def test_sync_history(client):
|
||||
r = client.get("/api/sync/history")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "runs" in data
|
||||
assert isinstance(data["runs"], list)
|
||||
|
||||
|
||||
def test_missing_skus(client):
|
||||
r = client.get("/api/validate/missing-skus")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "missing_skus" in data
|
||||
|
||||
|
||||
def test_mappings(client):
|
||||
r = client.get("/api/mappings")
|
||||
assert r.status_code == 200
|
||||
data = r.json()
|
||||
assert "mappings" in data
|
||||
|
||||
|
||||
def test_settings(client):
|
||||
r = client.get("/api/settings")
|
||||
assert r.status_code == 200
|
||||
assert isinstance(r.json(), dict)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("endpoint", ENDPOINTS)
|
||||
def test_response_time(client, endpoint):
|
||||
start = time.monotonic()
|
||||
client.get(endpoint)
|
||||
elapsed = time.monotonic() - start
|
||||
assert elapsed < 5.0, f"{endpoint} took {elapsed:.2f}s (limit: 5s)"
|
||||
Reference in New Issue
Block a user