166 lines
5.4 KiB
Python
166 lines
5.4 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from pathlib import Path
|
|
|
|
import testing.ci.quality_gate as quality_gate_module
|
|
from testing.ci.quality_gate import (
|
|
_js_node_issues,
|
|
_python_node_issues,
|
|
check_coverage,
|
|
check_file_sizes,
|
|
run_gate,
|
|
)
|
|
|
|
|
|
def test_check_file_sizes_flags_overlong_files(tmp_path: Path) -> None:
|
|
path = tmp_path / "tool.py"
|
|
path.write_text("\n".join(f"line {idx}" for idx in range(7)))
|
|
|
|
issues = check_file_sizes([path], max_lines=5)
|
|
|
|
assert issues and issues[0].check == "loc"
|
|
assert "exceeds 5" in issues[0].message
|
|
|
|
|
|
def test_docstring_helpers_accept_contract_comments_and_docstrings(tmp_path: Path) -> None:
|
|
py_path = tmp_path / "sample.py"
|
|
py_path.write_text(
|
|
'"""module docs"""\n\n'
|
|
'def documented():\n'
|
|
' """Explain what the helper does."""\n'
|
|
' return 1\n\n'
|
|
'def tiny_private_helper():\n'
|
|
' return 2\n\n'
|
|
'def missing_contract(value):\n'
|
|
' if value:\n'
|
|
' return value\n'
|
|
' if value == 0:\n'
|
|
' return "zero"\n'
|
|
' if value is None:\n'
|
|
' return "none"\n'
|
|
' if isinstance(value, str):\n'
|
|
' return value.strip()\n'
|
|
' return "fallback"\n'
|
|
)
|
|
js_path = tmp_path / "sample.js"
|
|
js_path.write_text(
|
|
'/**\n'
|
|
' * WHY: the helper needs a contract for the gate.\n'
|
|
' * @param {string} name - service name.\n'
|
|
' * @returns {string} icon label.\n'
|
|
' */\n'
|
|
'function pickIcon(name) {\n'
|
|
' return name;\n'
|
|
'}\n'
|
|
)
|
|
|
|
py_issues = _python_node_issues(py_path)
|
|
js_issues = _js_node_issues(js_path)
|
|
|
|
assert any(issue.message.endswith("missing_contract") for issue in py_issues)
|
|
assert js_issues == []
|
|
|
|
|
|
def test_check_coverage_reads_backend_and_frontend_reports(tmp_path: Path) -> None:
|
|
backend_report = tmp_path / "backend.xml"
|
|
backend_report.write_text(
|
|
'<coverage><packages><package><classes>'
|
|
'<class filename="backend/atlas_portal/app_factory.py" line-rate="1.0" branch-rate="1.0"/>'
|
|
'</classes></package></packages></coverage>'
|
|
)
|
|
frontend_report = tmp_path / "frontend.json"
|
|
frontend_report.write_text(
|
|
json.dumps(
|
|
{
|
|
"src/auth.js": {
|
|
"lines": {"pct": 100},
|
|
"statements": {"pct": 100},
|
|
"branches": {"pct": 100},
|
|
"functions": {"pct": 100},
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
issues = check_coverage(
|
|
[Path("backend/atlas_portal/app_factory.py"), Path("frontend/src/auth.js")],
|
|
backend_report=backend_report,
|
|
frontend_report=frontend_report,
|
|
threshold=95,
|
|
)
|
|
|
|
assert issues == []
|
|
|
|
|
|
def test_run_gate_reports_workspace_coverage_and_loc_totals(tmp_path: Path) -> None:
|
|
root_before = quality_gate_module.ROOT
|
|
quality_gate_module.ROOT = tmp_path
|
|
try:
|
|
backend_dir = tmp_path / "backend" / "atlas_portal"
|
|
frontend_dir = tmp_path / "frontend" / "src"
|
|
backend_dir.mkdir(parents=True)
|
|
frontend_dir.mkdir(parents=True)
|
|
|
|
(backend_dir / "app_factory.py").write_text(
|
|
'"""Factory docs."""\n\n'
|
|
"def create_app():\n"
|
|
' """Create the app."""\n'
|
|
" return object()\n"
|
|
)
|
|
(backend_dir / "too_long.py").write_text("\n".join(f"line_{idx}" for idx in range(501)))
|
|
(frontend_dir / "auth.js").write_text(
|
|
"/**\n"
|
|
" * WHY: auth wrapper exists.\n"
|
|
" * @param {string} token - jwt.\n"
|
|
" * @returns {string} token.\n"
|
|
" */\n"
|
|
"function auth(token) {\n"
|
|
" return token;\n"
|
|
"}\n"
|
|
)
|
|
|
|
backend_report = tmp_path / "backend.xml"
|
|
backend_report.write_text(
|
|
'<coverage><packages><package><classes>'
|
|
'<class filename="backend/atlas_portal/app_factory.py" line-rate="1.0" branch-rate="1.0"/>'
|
|
'</classes></package></packages></coverage>'
|
|
)
|
|
frontend_report = tmp_path / "frontend.json"
|
|
frontend_report.write_text(
|
|
json.dumps(
|
|
{
|
|
"src/auth.js": {
|
|
"lines": {"pct": 100},
|
|
"statements": {"pct": 100},
|
|
"branches": {"pct": 100},
|
|
"functions": {"pct": 100},
|
|
}
|
|
}
|
|
)
|
|
)
|
|
|
|
contract = {
|
|
"managed_files": [
|
|
"backend/atlas_portal/app_factory.py",
|
|
"backend/atlas_portal/too_long.py",
|
|
],
|
|
"docstring_files": ["backend/atlas_portal/app_factory.py"],
|
|
"coverage_files": [
|
|
"backend/atlas_portal/app_factory.py",
|
|
"frontend/src/auth.js",
|
|
],
|
|
"max_lines": 500,
|
|
"coverage_threshold_pct": 95,
|
|
}
|
|
contract_path = tmp_path / "contract.json"
|
|
contract_path.write_text(json.dumps(contract))
|
|
|
|
issues, report = run_gate(contract_path, backend_coverage=backend_report, frontend_coverage=frontend_report)
|
|
|
|
assert any(issue.check == "loc" for issue in issues)
|
|
assert report["workspace_line_coverage_percent"] == 100.0
|
|
assert report["source_lines_over_500"] == 1
|
|
finally:
|
|
quality_gate_module.ROOT = root_before
|