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:
93
api/tests/qa/test_qa_logs_monitor.py
Normal file
93
api/tests/qa/test_qa_logs_monitor.py
Normal file
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Log monitoring tests — parse app log files for errors and anomalies.
|
||||
Run with: pytest api/tests/qa/test_qa_logs_monitor.py
|
||||
"""
|
||||
import re
|
||||
|
||||
import pytest
|
||||
|
||||
pytestmark = pytest.mark.qa
|
||||
|
||||
# Log line format: 2026-03-23 07:57:12,691 | INFO | app.main | message
|
||||
_MAX_WARNINGS = 50
|
||||
|
||||
|
||||
def _read_lines(app_log_path):
|
||||
"""Read log file lines, skipping gracefully if file is missing."""
|
||||
if app_log_path is None or not app_log_path.exists():
|
||||
pytest.skip("No log file available")
|
||||
return app_log_path.read_text(encoding="utf-8", errors="replace").splitlines()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_log_file_exists(app_log_path):
|
||||
"""Log file path resolves to an existing file."""
|
||||
if app_log_path is None:
|
||||
pytest.skip("No log file configured")
|
||||
assert app_log_path.exists(), f"Log file not found: {app_log_path}"
|
||||
|
||||
|
||||
def test_no_critical_errors(app_log_path, qa_issues):
|
||||
"""No ERROR-level lines in the log."""
|
||||
lines = _read_lines(app_log_path)
|
||||
errors = [l for l in lines if "| ERROR |" in l]
|
||||
if errors:
|
||||
qa_issues.extend({"type": "log_error", "line": l} for l in errors)
|
||||
assert len(errors) == 0, (
|
||||
f"Found {len(errors)} ERROR line(s) in {app_log_path.name}:\n"
|
||||
+ "\n".join(errors[:10])
|
||||
)
|
||||
|
||||
|
||||
def test_no_oracle_errors(app_log_path, qa_issues):
|
||||
"""No Oracle ORA- error codes in the log."""
|
||||
lines = _read_lines(app_log_path)
|
||||
ora_errors = [l for l in lines if "ORA-" in l]
|
||||
if ora_errors:
|
||||
qa_issues.extend({"type": "oracle_error", "line": l} for l in ora_errors)
|
||||
assert len(ora_errors) == 0, (
|
||||
f"Found {len(ora_errors)} ORA- error(s) in {app_log_path.name}:\n"
|
||||
+ "\n".join(ora_errors[:10])
|
||||
)
|
||||
|
||||
|
||||
def test_no_unhandled_exceptions(app_log_path, qa_issues):
|
||||
"""No unhandled Python tracebacks in the log."""
|
||||
lines = _read_lines(app_log_path)
|
||||
tb_lines = [l for l in lines if "Traceback" in l]
|
||||
if tb_lines:
|
||||
qa_issues.extend({"type": "traceback", "line": l} for l in tb_lines)
|
||||
assert len(tb_lines) == 0, (
|
||||
f"Found {len(tb_lines)} Traceback(s) in {app_log_path.name}:\n"
|
||||
+ "\n".join(tb_lines[:10])
|
||||
)
|
||||
|
||||
|
||||
def test_no_import_failures(app_log_path, qa_issues):
|
||||
"""No import failure messages in the log."""
|
||||
lines = _read_lines(app_log_path)
|
||||
pattern = re.compile(r"import failed|Order.*failed", re.IGNORECASE)
|
||||
failures = [l for l in lines if pattern.search(l)]
|
||||
if failures:
|
||||
qa_issues.extend({"type": "import_failure", "line": l} for l in failures)
|
||||
assert len(failures) == 0, (
|
||||
f"Found {len(failures)} import failure(s) in {app_log_path.name}:\n"
|
||||
+ "\n".join(failures[:10])
|
||||
)
|
||||
|
||||
|
||||
def test_warning_count_acceptable(app_log_path, qa_issues):
|
||||
"""WARNING count is below acceptable threshold."""
|
||||
lines = _read_lines(app_log_path)
|
||||
warnings = [l for l in lines if "| WARNING |" in l]
|
||||
if len(warnings) >= _MAX_WARNINGS:
|
||||
qa_issues.append({
|
||||
"type": "high_warning_count",
|
||||
"count": len(warnings),
|
||||
"threshold": _MAX_WARNINGS,
|
||||
})
|
||||
assert len(warnings) < _MAX_WARNINGS, (
|
||||
f"Warning count {len(warnings)} exceeds threshold {_MAX_WARNINGS} "
|
||||
f"in {app_log_path.name}"
|
||||
)
|
||||
Reference in New Issue
Block a user