228 lines
8.0 KiB
Python
228 lines
8.0 KiB
Python
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
|