Release 1.7.0: Grafana catalog, ingress/IRM, tests
Some checks failed
CI / test (push) Successful in 57s
Deploy / deploy (push) Failing after 13s

This commit is contained in:
Alexandr
2026-04-03 13:53:19 +03:00
parent f275260b0d
commit 5788f995b9
29 changed files with 1956 additions and 67 deletions

286
tests/irm_db_fake.py Normal file
View File

@ -0,0 +1,286 @@
"""In-memory «пул» для тестов IRM без реального PostgreSQL."""
from __future__ import annotations
from dataclasses import dataclass, field
from datetime import datetime, timezone
from typing import Any
from uuid import UUID, uuid4
def _now() -> datetime:
return datetime.now(timezone.utc)
@dataclass
class Row:
"""Минимальная обёртка под asyncpg.Record (доступ по ключу)."""
_data: dict[str, Any]
def __getitem__(self, key: str) -> Any:
return self._data[key]
def get(self, key: str, default: Any = None) -> Any:
return self._data.get(key, default)
class IrmFakeConn:
def __init__(self, store: IrmFakeStore) -> None:
self.store = store
def _q(self, query: str) -> str:
return " ".join(query.split())
async def execute(self, query: str, *args: Any) -> str:
q = self._q(query)
if "INSERT INTO incidents" in q and "ingress_event_id" in q:
self.store.insert_incident_alert(
args[0], args[1], args[2], args[3], args[4]
)
return "INSERT 0 1"
raise AssertionError(f"execute not implemented: {q[:80]}")
async def fetchval(self, query: str, *args: Any) -> Any:
q = self._q(query)
if "count(*)" in q and "FROM incidents" in q and "escalation" not in q:
return len(self.store.incidents)
if "count(*)" in q and "FROM tasks" in q:
return len(self.store.tasks)
if "count(*)" in q and "escalation_policies" in q:
return sum(1 for p in self.store.policies.values() if p["enabled"])
if "SELECT 1 FROM incidents WHERE id" in q:
uid = args[0]
return 1 if uid in self.store.incidents else None
raise AssertionError(f"fetchval not implemented: {q[:100]}")
async def fetch(self, query: str, *args: Any) -> list[Row]:
q = self._q(query)
if "FROM incidents" in q and "ORDER BY created_at DESC" in q:
rows = sorted(self.store.incidents.values(), key=lambda x: x["created_at"], reverse=True)
if "grafana_org_slug =" in q and "service_name =" in q:
rows = [
r
for r in rows
if r.get("grafana_org_slug") == args[0]
and r.get("service_name") == args[1]
]
lim = args[2]
elif "grafana_org_slug =" in q:
rows = [r for r in rows if r.get("grafana_org_slug") == args[0]]
lim = args[1]
elif "service_name =" in q:
rows = [r for r in rows if r.get("service_name") == args[0]]
lim = args[1]
else:
lim = args[0]
return [Row(dict(r)) for r in rows[:lim]]
if "FROM tasks" in q and "WHERE incident_id" in q and "ORDER BY" in q:
iid, lim = args[0], args[1]
match = [t for t in self.store.tasks.values() if t["incident_id"] == iid]
match.sort(key=lambda x: x["created_at"], reverse=True)
return [Row(dict(t)) for t in match[:lim]]
if "FROM tasks" in q and "WHERE incident_id" in q:
iid, lim = args[0], args[1]
match = [t for t in self.store.tasks.values() if t["incident_id"] == iid]
match.sort(key=lambda x: x["created_at"], reverse=True)
return [Row(dict(t)) for t in match[:lim]]
if "FROM tasks t" in q or ("FROM tasks" in q and "ORDER BY t.created_at" in q):
lim = args[0]
rows = sorted(self.store.tasks.values(), key=lambda x: x["created_at"], reverse=True)[:lim]
return [Row(dict(r)) for r in rows]
if "FROM tasks" in q and "ORDER BY created_at DESC" in q and "WHERE" not in q:
lim = args[0]
rows = sorted(self.store.tasks.values(), key=lambda x: x["created_at"], reverse=True)[:lim]
return [Row(dict(r)) for r in rows]
if "FROM escalation_policies" in q and "ORDER BY name" in q:
rows = sorted(self.store.policies.values(), key=lambda x: x["name"])
return [Row(dict(r)) for r in rows]
raise AssertionError(f"fetch not implemented: {q[:120]}")
async def fetchrow(self, query: str, *args: Any) -> Row | None:
q = self._q(query)
if "INSERT INTO incidents" in q and "VALUES ($1, $2, $3, 'manual'" in q:
return Row(self.store.insert_incident_manual(args[0], args[1], args[2]))
if "FROM incidents WHERE id" in q and "UPDATE" not in q and "/tasks" not in query.lower():
return self.store.get_incident(args[0])
if "UPDATE incidents SET" in q:
return self.store.update_incident(args[0], args[1], args[2], args[3])
if "INSERT INTO tasks" in q:
return Row(self.store.insert_task(args[0], args[1]))
if "FROM tasks WHERE id" in q and "UPDATE" not in q:
tid = args[0]
t = self.store.tasks.get(tid)
return Row(dict(t)) if t else None
if "UPDATE tasks SET" in q:
return self.store.update_task(args[0], args[1], args[2])
if "INSERT INTO escalation_policies" in q:
return Row(self.store.insert_policy(args[0], args[1], args[2]))
if "FROM escalation_policies WHERE id" in q and "UPDATE" not in q and "DELETE" not in q:
pid = args[0]
p = self.store.policies.get(pid)
return Row(dict(p)) if p else None
if "UPDATE escalation_policies SET" in q:
return self.store.update_policy(args[0], args[1], args[2], args[3])
if "DELETE FROM escalation_policies" in q:
return self.store.delete_policy(args[0])
raise AssertionError(f"fetchrow not implemented: {q[:120]}")
@dataclass
class IrmFakeStore:
incidents: dict[UUID, dict[str, Any]] = field(default_factory=dict)
tasks: dict[UUID, dict[str, Any]] = field(default_factory=dict)
policies: dict[UUID, dict[str, Any]] = field(default_factory=dict)
def insert_incident_alert(
self,
title: str,
sev: str,
ingress_id: UUID,
grafana_org_slug: Any,
service_name: Any,
) -> None:
iid = uuid4()
now = _now()
self.incidents[iid] = {
"id": iid,
"title": title,
"status": "open",
"severity": sev,
"source": "grafana",
"ingress_event_id": ingress_id,
"created_at": now,
"updated_at": now,
"grafana_org_slug": grafana_org_slug,
"service_name": service_name,
}
def insert_incident_manual(self, title: str, status: str, severity: str) -> dict[str, Any]:
iid = uuid4()
now = _now()
row = {
"id": iid,
"title": title,
"status": status,
"severity": severity,
"source": "manual",
"ingress_event_id": None,
"created_at": now,
"updated_at": now,
"grafana_org_slug": None,
"service_name": None,
}
self.incidents[iid] = row
return row
def get_incident(self, iid: UUID) -> Row | None:
r = self.incidents.get(iid)
return Row(dict(r)) if r else None
def update_incident(
self,
iid: UUID,
title: str | None,
status: str | None,
severity: str | None,
) -> Row | None:
r = self.incidents.get(iid)
if not r:
return None
if title is not None:
r["title"] = title
if status is not None:
r["status"] = status
if severity is not None:
r["severity"] = severity
r["updated_at"] = _now()
return Row(dict(r))
def insert_task(self, title: str, incident_id: UUID | None) -> dict[str, Any]:
tid = uuid4()
now = _now()
row = {
"id": tid,
"incident_id": incident_id,
"title": title,
"status": "open",
"created_at": now,
}
self.tasks[tid] = row
return row
def update_task(self, tid: UUID, title: str | None, status: str | None) -> Row | None:
r = self.tasks.get(tid)
if not r:
return None
if title is not None:
r["title"] = title
if status is not None:
r["status"] = status
return Row(dict(r))
def insert_policy(self, name: str, enabled: bool, steps_json: str) -> dict[str, Any]:
import json
pid = uuid4()
now = _now()
steps = json.loads(steps_json)
row = {
"id": pid,
"name": name,
"enabled": enabled,
"steps": steps,
"created_at": now,
}
self.policies[pid] = row
return row
def update_policy(
self,
pid: UUID,
name: str | None,
enabled: bool | None,
steps_json: str | None,
) -> Row | None:
import json
r = self.policies.get(pid)
if not r:
return None
if name is not None:
r["name"] = name
if enabled is not None:
r["enabled"] = enabled
if steps_json is not None:
r["steps"] = json.loads(steps_json)
return Row(dict(r))
def delete_policy(self, pid: UUID) -> Row | None:
if pid not in self.policies:
return None
self.policies.pop(pid)
return Row({"id": pid})
class IrmFakeAcquire:
def __init__(self, store: IrmFakeStore) -> None:
self.store = store
async def __aenter__(self) -> IrmFakeConn:
return IrmFakeConn(self.store)
async def __aexit__(self, *args: Any) -> None:
pass
class IrmFakePool:
def __init__(self, store: IrmFakeStore | None = None) -> None:
self._store = store or IrmFakeStore()
def acquire(self) -> IrmFakeAcquire:
return IrmFakeAcquire(self._store)
@property
def store(self) -> IrmFakeStore:
return self._store