from __future__ import annotations from datetime import datetime, timezone import types import httpx from prometheus_client import REGISTRY from ariadne.services import jenkins_build_weather as weather_module class _DummyResponse: def __init__(self, payload: dict[str, object], status_code: int = 200) -> None: self._payload = payload self.status_code = status_code def raise_for_status(self) -> None: if self.status_code >= 400: request = httpx.Request("GET", "https://ci.bstein.dev/api/json") response = httpx.Response(self.status_code, request=request) raise httpx.HTTPStatusError("boom", request=request, response=response) def json(self) -> dict[str, object]: return self._payload class _DummyClient: def __init__(self, payload: dict[str, object]) -> None: self._payload = payload self.called = False def __enter__(self) -> _DummyClient: return self def __exit__(self, exc_type, exc, tb) -> bool: return False def get(self, url: str, params: dict[str, str] | None = None) -> _DummyResponse: self.called = True assert url == "https://ci.bstein.dev/api/json" assert isinstance(params, dict) assert "tree" in params return _DummyResponse(self._payload) def _metric_value(name: str, labels: dict[str, str] | None = None) -> float | None: value = REGISTRY.get_sample_value(name, labels or {}) return float(value) if value is not None else None def _dummy_settings(base_url: str = "https://ci.bstein.dev") -> types.SimpleNamespace: return types.SimpleNamespace( jenkins_base_url=base_url, jenkins_api_user="", jenkins_api_token="", jenkins_api_timeout_sec=5.0, ) def test_collect_jenkins_build_weather_records_metrics(monkeypatch) -> None: weather_module._JOB_SERIES = set() monkeypatch.setattr(weather_module, "settings", _dummy_settings()) payload = { "jobs": [ { "name": "ariadne", "url": "https://ci.bstein.dev/job/ariadne/", "color": "blue", "healthReport": [{"score": 93}], "lastBuild": {"result": "SUCCESS", "timestamp": 1713000000000, "duration": 186000}, "lastSuccessfulBuild": {"timestamp": 1713000000000}, "lastFailedBuild": {"timestamp": 1712000000000}, }, { "name": "titan-iac", "url": "https://ci.bstein.dev/job/titan-iac/", "color": "red", "healthReport": [{"score": 11}], "lastBuild": {"result": "FAILURE", "timestamp": 1712990000000, "duration": 126000}, "lastSuccessfulBuild": {"timestamp": 1711000000000}, "lastFailedBuild": {"timestamp": 1712990000000}, }, ] } monkeypatch.setattr(weather_module.httpx, "Client", lambda **_kwargs: _DummyClient(payload)) before = _metric_value("ariadne_jenkins_build_weather_runs_total", {"status": "ok"}) or 0.0 summary = weather_module.collect_jenkins_build_weather() assert summary.jobs_total == 2 assert summary.success_total == 1 assert summary.failure_total == 1 assert summary.running_total == 0 assert summary.unknown_total == 0 assert (_metric_value("ariadne_jenkins_build_weather_runs_total", {"status": "ok"}) or 0.0) == before + 1 assert _metric_value( "ariadne_jenkins_build_weather_job_last_status", { "job": "ariadne", "job_url": "https://ci.bstein.dev/job/ariadne/", "weather_icon": "☀️", }, ) == 1.0 assert _metric_value( "ariadne_jenkins_build_weather_job_last_status", { "job": "titan-iac", "job_url": "https://ci.bstein.dev/job/titan-iac/", "weather_icon": "⛈️", }, ) == 0.0 assert _metric_value( "ariadne_jenkins_build_weather_job_last_duration_seconds", { "job": "ariadne", "job_url": "https://ci.bstein.dev/job/ariadne/", "weather_icon": "☀️", }, ) == 186.0 def test_collect_jenkins_build_weather_removes_deleted_job_series(monkeypatch) -> None: weather_module._JOB_SERIES = set() monkeypatch.setattr(weather_module, "settings", _dummy_settings()) first_payload = { "jobs": [ { "name": "ariadne", "url": "https://ci.bstein.dev/job/ariadne/", "color": "blue", "healthReport": [{"score": 90}], "lastBuild": {"result": "SUCCESS", "timestamp": 1713000000000, "duration": 186000}, "lastSuccessfulBuild": {"timestamp": 1713000000000}, "lastFailedBuild": {"timestamp": 1712000000000}, }, { "name": "pegasus", "url": "https://ci.bstein.dev/job/pegasus/", "color": "yellow", "healthReport": [{"score": 50}], "lastBuild": {"result": "FAILURE", "timestamp": 1712980000000, "duration": 120000}, "lastSuccessfulBuild": {"timestamp": 1710000000000}, "lastFailedBuild": {"timestamp": 1712980000000}, }, ] } second_payload = { "jobs": [ { "name": "ariadne", "url": "https://ci.bstein.dev/job/ariadne/", "color": "blue", "healthReport": [{"score": 90}], "lastBuild": {"result": "SUCCESS", "timestamp": 1713010000000, "duration": 184000}, "lastSuccessfulBuild": {"timestamp": 1713010000000}, "lastFailedBuild": {"timestamp": 1712000000000}, } ] } payloads = [first_payload, second_payload] monkeypatch.setattr( weather_module.httpx, "Client", lambda **_kwargs: _DummyClient(payloads.pop(0)), ) weather_module.collect_jenkins_build_weather() weather_module.collect_jenkins_build_weather() assert _metric_value( "ariadne_jenkins_build_weather_job_last_status", { "job": "pegasus", "job_url": "https://ci.bstein.dev/job/pegasus/", "weather_icon": "☁️", }, ) is None def test_collect_jenkins_build_weather_skips_when_base_url_empty(monkeypatch) -> None: weather_module._JOB_SERIES = set() monkeypatch.setattr(weather_module, "settings", _dummy_settings(base_url="")) before = _metric_value("ariadne_jenkins_build_weather_runs_total", {"status": "skipped"}) or 0.0 summary = weather_module.collect_jenkins_build_weather() assert summary.jobs_total == 0 assert (_metric_value("ariadne_jenkins_build_weather_runs_total", {"status": "skipped"}) or 0.0) == before + 1 def test_fetch_jobs_flattens_folder_jobs(monkeypatch) -> None: weather_module._JOB_SERIES = set() monkeypatch.setattr(weather_module, "settings", _dummy_settings()) payload = { "jobs": [ { "name": "folder", "url": "https://ci.bstein.dev/job/folder/", "jobs": [ { "name": "child", "url": "https://ci.bstein.dev/job/folder/job/child/", "color": "blue", "healthReport": [{"score": 100}], "lastBuild": {"result": "SUCCESS", "timestamp": 1713000000000, "duration": 1000}, "lastSuccessfulBuild": {"timestamp": 1713000000000}, "lastFailedBuild": {"timestamp": 1712000000000}, } ], } ] } monkeypatch.setattr(weather_module.httpx, "Client", lambda **_kwargs: _DummyClient(payload)) jobs = weather_module._fetch_jobs() assert len(jobs) == 1 assert jobs[0].job == "folder/child" assert jobs[0].status == "success" assert jobs[0].last_duration_seconds == 1.0 assert datetime.fromtimestamp(jobs[0].last_run_ts, tz=timezone.utc).year == 2024