diff --git a/testing/quality_contract.json b/testing/quality_contract.json
index 06741e18..371e8e1e 100644
--- a/testing/quality_contract.json
+++ b/testing/quality_contract.json
@@ -16,20 +16,27 @@
],
"managed_modules": [
"ci/scripts/publish_test_metrics.py",
- "services/mailu/scripts/mailu_sync.py",
+ "ci/scripts/publish_test_metrics_quality.py",
"testing/__init__.py",
"testing/quality_contract.py",
"testing/quality_docs.py",
"testing/quality_hygiene.py",
"testing/quality_coverage.py",
- "testing/quality_gate.py"
+ "testing/quality_gate.py",
+ "ci/tests/glue/test_ariadne_schedules.py",
+ "ci/tests/glue/test_glue_metrics.py",
+ "testing/tests/test_publish_test_metrics.py",
+ "testing/tests/test_quality_contract.py",
+ "testing/tests/test_quality_gate.py"
],
"lint_paths": [
"ci/scripts/publish_test_metrics.py",
+ "ci/scripts/publish_test_metrics_quality.py",
"ci/tests/glue",
"scripts/tests",
"services/comms/scripts/tests",
"services/mailu/scripts/mailu_sync.py",
+ "testing/tests",
"testing"
],
"pytest_suites": {
@@ -70,6 +77,8 @@
"hygiene",
"unit",
"coverage",
+ "sonarqube",
+ "ironbank",
"glue"
]
},
@@ -151,6 +160,7 @@
"minimum_percent": 95.0,
"tracked_files": [
"ci/scripts/publish_test_metrics.py",
+ "ci/scripts/publish_test_metrics_quality.py",
"testing/quality_contract.py",
"testing/quality_docs.py",
"testing/quality_hygiene.py",
diff --git a/testing/tests/test_publish_test_metrics_quality.py b/testing/tests/test_publish_test_metrics_quality.py
new file mode 100644
index 00000000..2b6d2cb7
--- /dev/null
+++ b/testing/tests/test_publish_test_metrics_quality.py
@@ -0,0 +1,105 @@
+"""Focused tests for publish_test_metrics quality helper fallbacks."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from ci.scripts import publish_test_metrics_quality as quality_helpers
+
+
+def test_infer_workspace_coverage_percent_handles_candidate_paths_and_parse_failures(tmp_path: Path) -> None:
+ """Coverage inference should honor explicit XML hints and fail closed on bad XML."""
+
+ build_dir = tmp_path / "build"
+ build_dir.mkdir()
+
+ coverage_xml = build_dir / "custom-coverage.xml"
+ coverage_xml.write_text('', encoding="utf-8")
+ summary = {"results": [None, {"name": "coverage", "coverage_xml": str(coverage_xml)}]}
+ assert quality_helpers._infer_workspace_coverage_percent(summary, "build/default.xml") == 97.5
+
+ missing_xml = build_dir / "missing.xml"
+ assert quality_helpers._infer_workspace_coverage_percent({}, str(missing_xml)) == 0.0
+
+ no_rate_xml = build_dir / "no-rate.xml"
+ no_rate_xml.write_text("", encoding="utf-8")
+ assert quality_helpers._infer_workspace_coverage_percent({}, str(no_rate_xml)) == 0.0
+
+ bad_xml = build_dir / "bad.xml"
+ bad_xml.write_text(" None:
+ """LOC inference should ignore non-lists and only count explicit over-limit strings."""
+
+ summary = {
+ "results": [
+ None,
+ {"name": "docs", "issues": ["ignore me"]},
+ {"name": "loc", "issues": "not-a-list"},
+ {
+ "name": "hygiene",
+ "issues": [
+ "file exceeds 500 LOC: alpha.py (501)",
+ 42,
+ "naming rule failed: beta.py",
+ "file exceeds 500 LOC: gamma.py (650)",
+ ],
+ },
+ ]
+ }
+
+ assert quality_helpers._infer_source_lines_over_500(summary) == 2
+
+
+def test_build_check_statuses_uses_fallbacks_for_tests_docs_and_gate_glue() -> None:
+ """Fallback status synthesis should derive missing checks from related signals."""
+
+ statuses = quality_helpers._build_check_statuses(
+ summary={
+ "results": [
+ {"name": "docs", "status": "ok"},
+ {"name": "smell", "status": "ok"},
+ {"name": "gate", "status": "warning"},
+ ]
+ },
+ tests={"tests": 3, "failures": 1, "errors": 0, "skipped": 0},
+ workspace_line_coverage_percent=0.0,
+ source_lines_over_500=0,
+ sonarqube_report={},
+ supply_chain_report={},
+ supply_chain_required=False,
+ )
+
+ assert statuses["tests"] == "failed"
+ assert statuses["coverage"] == "not_applicable"
+ assert statuses["loc"] == "ok"
+ assert statuses["docs_naming"] == "ok"
+ assert statuses["gate_glue"] == "failed"
+
+
+def test_build_check_statuses_preserves_explicit_loc_docs_and_glue_results() -> None:
+ """Explicit canonical statuses should win over fallback inference."""
+
+ statuses = quality_helpers._build_check_statuses(
+ summary={
+ "results": [
+ {"name": "loc", "status": "ok"},
+ {"name": "docs_naming", "status": "ok"},
+ {"name": "gate_glue", "status": "ok"},
+ ]
+ },
+ tests={"tests": 0, "failures": 0, "errors": 0, "skipped": 0},
+ workspace_line_coverage_percent=96.0,
+ source_lines_over_500=9,
+ sonarqube_report={},
+ supply_chain_report={},
+ supply_chain_required=False,
+ )
+
+ assert statuses["tests"] == "not_applicable"
+ assert statuses["coverage"] == "ok"
+ assert statuses["loc"] == "ok"
+ assert statuses["docs_naming"] == "ok"
+ assert statuses["gate_glue"] == "ok"
diff --git a/testing/tests/test_quality_coverage_helpers.py b/testing/tests/test_quality_coverage_helpers.py
new file mode 100644
index 00000000..5eaa624e
--- /dev/null
+++ b/testing/tests/test_quality_coverage_helpers.py
@@ -0,0 +1,76 @@
+"""Focused coverage-helper tests for titan-iac quality modules."""
+
+from __future__ import annotations
+
+import textwrap
+from pathlib import Path
+
+from testing.quality_coverage import compute_workspace_line_coverage, run_check
+
+
+def test_compute_workspace_line_coverage_handles_missing_xml(tmp_path: Path) -> None:
+ """Missing coverage XML should produce a zero workspace coverage score."""
+
+ contract = {"coverage": {"tracked_files": ["managed.py"]}}
+ assert compute_workspace_line_coverage(contract, tmp_path, tmp_path / "missing.xml") == 0.0
+
+
+def test_compute_workspace_line_coverage_averages_present_tracked_files(tmp_path: Path) -> None:
+ """Workspace coverage should average only tracked files that appear in the report."""
+
+ coverage_xml = tmp_path / "coverage.xml"
+ coverage_xml.write_text(
+ textwrap.dedent(
+ """\
+
+
+
+
+
+
+
+
+
+
+ """
+ ),
+ encoding="utf-8",
+ )
+
+ contract = {"coverage": {"tracked_files": ["alpha.py", "beta.py", "missing.py"]}}
+ assert compute_workspace_line_coverage(contract, tmp_path, coverage_xml) == 75.0
+
+
+def test_run_check_keeps_relative_names_when_source_roots_do_not_match(tmp_path: Path) -> None:
+ """Relative filenames should remain relative when no declared source root contains them."""
+
+ coverage_xml = tmp_path / "coverage.xml"
+ unmatched_root = tmp_path / "other-root"
+ unmatched_root.mkdir()
+ coverage_xml.write_text(
+ textwrap.dedent(
+ f"""\
+
+
+ {unmatched_root}
+
+
+
+
+
+
+
+
+
+ """
+ ),
+ encoding="utf-8",
+ )
+
+ issues = run_check(
+ {"coverage": {"minimum_percent": 95.0, "tracked_files": ["relative.py"]}},
+ tmp_path,
+ coverage_xml,
+ )
+
+ assert issues == ["coverage below 95.0%: relative.py (80.0%)"]
diff --git a/testing/tests/test_quality_hygiene_helpers.py b/testing/tests/test_quality_hygiene_helpers.py
new file mode 100644
index 00000000..a303c650
--- /dev/null
+++ b/testing/tests/test_quality_hygiene_helpers.py
@@ -0,0 +1,26 @@
+"""Focused hygiene-helper tests for titan-iac quality modules."""
+
+from __future__ import annotations
+
+from pathlib import Path
+
+from testing.quality_hygiene import count_files_over_line_limit
+
+
+def test_count_files_over_line_limit_counts_only_long_matches(tmp_path: Path) -> None:
+ """Only files beyond the configured limit should contribute to the LOC total."""
+
+ tests_dir = tmp_path / "tests"
+ tests_dir.mkdir()
+ (tests_dir / "test_short.py").write_text("line\n", encoding="utf-8")
+ (tests_dir / "test_long.py").write_text("line\n" * 4, encoding="utf-8")
+ (tests_dir / "notes.txt").write_text("line\n" * 8, encoding="utf-8")
+
+ contract = {
+ "hygiene": {
+ "max_lines": 3,
+ "line_limit_globs": ["tests/*.py"],
+ }
+ }
+
+ assert count_files_over_line_limit(contract, tmp_path) == 1