merge master into ariadne hygiene branch
This commit is contained in:
commit
7f284007eb
@ -57,12 +57,18 @@ class KeycloakOIDC:
|
||||
def _decode_claims(self, token: str, key: dict[str, Any]) -> dict[str, Any]:
|
||||
return jwt.decode(
|
||||
token,
|
||||
key=jwt.algorithms.RSAAlgorithm.from_jwk(key),
|
||||
key=self._key_from_jwk(key),
|
||||
algorithms=["RS256"],
|
||||
options={"verify_aud": False},
|
||||
issuer=self._issuer,
|
||||
)
|
||||
|
||||
def _key_from_jwk(self, key: dict[str, Any]) -> Any:
|
||||
algorithm = getattr(jwt.algorithms, "RSAAlgorithm", None)
|
||||
if algorithm and hasattr(algorithm, "from_jwk"):
|
||||
return algorithm.from_jwk(key)
|
||||
return jwt.PyJWK.from_dict(key).key
|
||||
|
||||
def _validate_audience(self, claims: dict[str, Any]) -> None:
|
||||
azp = claims.get("azp")
|
||||
aud = claims.get("aud")
|
||||
|
||||
@ -8,29 +8,45 @@ import ast
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def _is_dataclass_class(node: ast.ClassDef) -> bool:
|
||||
"""Return whether a class uses the dataclass decorator."""
|
||||
|
||||
return any(
|
||||
(isinstance(dec, ast.Name) and dec.id == "dataclass")
|
||||
or (isinstance(dec, ast.Call) and isinstance(dec.func, ast.Name) and dec.func.id == "dataclass")
|
||||
for dec in node.decorator_list
|
||||
)
|
||||
|
||||
|
||||
def _base_names(node: ast.ClassDef) -> set[str]:
|
||||
"""Return simple base class names used by a class definition."""
|
||||
|
||||
return {base.id for base in node.bases if isinstance(base, ast.Name)}
|
||||
|
||||
|
||||
def _needs_function_docstring(node: ast.FunctionDef | ast.AsyncFunctionDef, parent_class: str | None) -> bool:
|
||||
"""Return whether a public function-like node needs a docstring."""
|
||||
|
||||
if node.name.startswith("_") and node.name != "__init__":
|
||||
return False
|
||||
return not (parent_class and node.name.startswith("_"))
|
||||
|
||||
|
||||
def _needs_class_docstring(node: ast.ClassDef) -> bool:
|
||||
"""Return whether a public class-like node needs a docstring."""
|
||||
|
||||
bases = _base_names(node)
|
||||
skipped_bases = {"Exception", "RuntimeError", "BaseException", "BaseModel"}
|
||||
return not (node.name.startswith("_") or _is_dataclass_class(node) or bool(bases.intersection(skipped_bases)))
|
||||
|
||||
|
||||
def _needs_docstring(node: ast.AST, *, parent_class: str | None = None) -> bool:
|
||||
"""Return whether `node` should carry an API contract docstring."""
|
||||
|
||||
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
||||
name = node.name
|
||||
if name.startswith("_") and name != "__init__":
|
||||
return False
|
||||
return not (parent_class and name.startswith("_"))
|
||||
return _needs_function_docstring(node, parent_class)
|
||||
if isinstance(node, ast.ClassDef):
|
||||
if node.name.startswith("_"):
|
||||
return False
|
||||
if any(
|
||||
(isinstance(dec, ast.Name) and dec.id == "dataclass")
|
||||
or (isinstance(dec, ast.Call) and isinstance(dec.func, ast.Name) and dec.func.id == "dataclass")
|
||||
for dec in node.decorator_list
|
||||
):
|
||||
return False
|
||||
if any(
|
||||
isinstance(base, ast.Name) and base.id in {"Exception", "RuntimeError", "BaseException"}
|
||||
for base in node.bases
|
||||
):
|
||||
return False
|
||||
return not any(isinstance(base, ast.Name) and base.id == "BaseModel" for base in node.bases)
|
||||
return _needs_class_docstring(node)
|
||||
return False
|
||||
|
||||
|
||||
|
||||
@ -204,11 +204,31 @@ def _supply_chain_check_status(build_dir: Path) -> str:
|
||||
return "failed"
|
||||
|
||||
|
||||
def _resolve_artifact_paths(repo_root: Path) -> tuple[Path, Path]:
|
||||
"""Find coverage and JUnit artifacts even when a test runner uses fallback names."""
|
||||
|
||||
coverage_path = Path(os.getenv("COVERAGE_JSON", "build/coverage.json"))
|
||||
junit_path = Path(os.getenv("JUNIT_XML", "build/junit.xml"))
|
||||
if not coverage_path.exists():
|
||||
for candidate in (
|
||||
repo_root / "build" / "coverage.json",
|
||||
repo_root / "build" / "coverage-summary.json",
|
||||
repo_root / "build" / "coverage" / "coverage-summary.json",
|
||||
):
|
||||
if candidate.exists():
|
||||
coverage_path = candidate
|
||||
break
|
||||
if not junit_path.exists():
|
||||
junit_candidates = sorted((repo_root / "build").glob("junit*.xml"))
|
||||
if junit_candidates:
|
||||
junit_path = junit_candidates[0]
|
||||
return coverage_path, junit_path
|
||||
|
||||
|
||||
def main() -> int:
|
||||
repo_root = Path(__file__).resolve().parents[1]
|
||||
build_dir = repo_root / "build"
|
||||
coverage_path = os.getenv("COVERAGE_JSON", "build/coverage.json")
|
||||
junit_path = os.getenv("JUNIT_XML", "build/junit.xml")
|
||||
coverage_path, junit_path = _resolve_artifact_paths(repo_root)
|
||||
pushgateway_url = os.getenv(
|
||||
"PUSHGATEWAY_URL", "http://platform-quality-gateway.monitoring.svc.cluster.local:9091"
|
||||
).strip()
|
||||
@ -217,16 +237,19 @@ def main() -> int:
|
||||
build_number = os.getenv("BUILD_NUMBER", "")
|
||||
commit = os.getenv("GIT_COMMIT", "")
|
||||
|
||||
print(f"[metrics] coverage_path={coverage_path} exists={coverage_path.exists()}")
|
||||
print(f"[metrics] junit_path={junit_path} exists={junit_path.exists()}")
|
||||
|
||||
coverage = 0.0
|
||||
if os.path.exists(coverage_path):
|
||||
coverage = _load_coverage(coverage_path)
|
||||
if coverage_path.exists():
|
||||
coverage = _load_coverage(str(coverage_path))
|
||||
docs_gate_rc = _load_gate_rc(Path(os.getenv("QUALITY_GATE_DOCS_RC_PATH", str(build_dir / "docs-naming.rc"))))
|
||||
source_lines_over_500 = _count_source_files_over_limit(repo_root, max_lines=500)
|
||||
totals = {"tests": 0, "failures": 0, "errors": 0, "skipped": 0}
|
||||
test_cases: list[tuple[str, str]] = []
|
||||
if os.path.exists(junit_path):
|
||||
totals = _load_junit(junit_path)
|
||||
test_cases = _load_junit_cases(junit_path)
|
||||
if junit_path.exists():
|
||||
totals = _load_junit(str(junit_path))
|
||||
test_cases = _load_junit_cases(str(junit_path))
|
||||
passed = max(totals["tests"] - totals["failures"] - totals["errors"] - totals["skipped"], 0)
|
||||
|
||||
outcome = "ok"
|
||||
@ -284,10 +307,15 @@ def main() -> int:
|
||||
"# TYPE ariadne_quality_gate_build_info gauge",
|
||||
f"ariadne_quality_gate_build_info{_label_str(labels)} 1",
|
||||
]
|
||||
payload_lines.extend(
|
||||
f'platform_quality_gate_test_case_result{{suite="{suite}",test="{_escape_label(test_name)}",status="{_escape_label(test_status)}"}} 1'
|
||||
for test_name, test_status in test_cases
|
||||
)
|
||||
if test_cases:
|
||||
payload_lines.extend(
|
||||
f'platform_quality_gate_test_case_result{{suite="{suite}",test="{_escape_label(test_name)}",status="{_escape_label(test_status)}"}} 1'
|
||||
for test_name, test_status in test_cases
|
||||
)
|
||||
else:
|
||||
payload_lines.append(
|
||||
f'platform_quality_gate_test_case_result{{suite="{suite}",test="__no_test_cases__",status="skipped"}} 1'
|
||||
)
|
||||
payload_lines.extend(
|
||||
f'ariadne_quality_gate_checks_total{{suite="{suite}",check="{check_name}",result="{check_status}"}} 1'
|
||||
for check_name, check_status in checks.items()
|
||||
|
||||
@ -20,7 +20,7 @@ def test_keycloak_verify_accepts_matching_audience(monkeypatch) -> None:
|
||||
kc = KeycloakOIDC("https://jwks", "https://issuer", "portal")
|
||||
|
||||
monkeypatch.setattr(kc, "_get_jwks", lambda force=False: {"keys": [{"kid": "test"}]})
|
||||
monkeypatch.setattr(jwt.algorithms.RSAAlgorithm, "from_jwk", lambda key: "dummy")
|
||||
monkeypatch.setattr(kc, "_key_from_jwk", lambda key: "dummy")
|
||||
monkeypatch.setattr(
|
||||
jwt,
|
||||
"decode",
|
||||
@ -36,7 +36,7 @@ def test_keycloak_verify_rejects_wrong_audience(monkeypatch) -> None:
|
||||
kc = KeycloakOIDC("https://jwks", "https://issuer", "portal")
|
||||
|
||||
monkeypatch.setattr(kc, "_get_jwks", lambda force=False: {"keys": [{"kid": "test"}]})
|
||||
monkeypatch.setattr(jwt.algorithms.RSAAlgorithm, "from_jwk", lambda key: "dummy")
|
||||
monkeypatch.setattr(kc, "_key_from_jwk", lambda key: "dummy")
|
||||
monkeypatch.setattr(
|
||||
jwt,
|
||||
"decode",
|
||||
@ -73,7 +73,7 @@ def test_keycloak_verify_refreshes_jwks(monkeypatch) -> None:
|
||||
return {"keys": [{"kid": "test"}]}
|
||||
|
||||
monkeypatch.setattr(kc, "_get_jwks", fake_get_jwks)
|
||||
monkeypatch.setattr(jwt.algorithms.RSAAlgorithm, "from_jwk", lambda key: "dummy")
|
||||
monkeypatch.setattr(kc, "_key_from_jwk", lambda key: "dummy")
|
||||
monkeypatch.setattr(
|
||||
jwt,
|
||||
"decode",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user