Files
FabledSteward/tests/integration/test_cleanup.py
T
bvandeusenandClaude Opus 5.5 9fdd67a675
CI / lint (push) Successful in 2s
CI / integration (push) Successful in 47s
CI / unit (push) Successful in 1m10s
CI / publish (push) Successful in 55s
feat(retention): every growing table is pruned; the audit log has its own window; charts read the raw window from Settings (#5155)
- prune_by_age(column) registers a time-series column with the hourly cleanup;
  Traefik (metrics, access summaries, global stats) and UniFi (WAN, client,
  WLAN, DPI, events, alarms, speedtests) register theirs in setup().
- Alert events join monitor results and Ansible runs on data.retention_days,
  read from Settings each run; Ansible run log files older than it are deleted.
- audit.retention_days (365) keeps the audit log longer, set on Settings > General.
- metric_series.points reads metrics.retention.raw_days, so the chart's
  raw/rollup seam is where the rollup put it (RAW_DAYS removed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 21:05:22 -04:00

80 lines
3.1 KiB
Python

"""Integration: retention windows read from Settings reach the pruning (#5155)."""
from __future__ import annotations
import asyncio
import os
import uuid
from datetime import datetime, timedelta, timezone
import pytest
pytestmark = pytest.mark.integration
@pytest.fixture
def app():
if not os.environ.get("STEWARD_DATABASE_URL"):
pytest.skip("needs Postgres")
from steward.app import create_app
return create_app(testing=False)
def test_the_audit_window_comes_from_settings(app):
from sqlalchemy import text
from steward.core.cleanup import run_cleanup
from steward.core.settings import set_setting
from steward.models.audit import AuditEvent
now = datetime.now(timezone.utc)
tag = f"test.cleanup.{uuid.uuid4().hex[:8]}"
async def _go():
async with app.db_sessionmaker() as s:
async with s.begin():
await set_setting(s, "audit.retention_days", 30)
s.add(AuditEvent(action=tag, entity_id="old", timestamp=now - timedelta(days=40)))
s.add(AuditEvent(action=tag, entity_id="new", timestamp=now - timedelta(days=20)))
try:
await run_cleanup(app)
finally:
async with app.db_sessionmaker() as s:
async with s.begin():
await set_setting(s, "audit.retention_days", 365)
async with app.db_sessionmaker() as s:
return [r[0] for r in (await s.execute(text(
"SELECT entity_id FROM audit_events WHERE action = :a"), {"a": tag})).all()]
assert asyncio.run(_go()) == ["new"]
def test_the_chart_seam_follows_the_raw_metrics_setting(app):
# With raw_days 1, a range two days back reads the hourly rollup there and
# not the raw sample beside it; at the default 7 it would read the raw one.
from steward.core import metric_series
from steward.core.settings import set_setting
from steward.models.metrics import PluginMetric, PluginMetricHourly
now = datetime.now(timezone.utc)
two_days = (now - timedelta(days=2)).replace(minute=0, second=0, microsecond=0)
res = f"seam-{uuid.uuid4().hex[:8]}"
async def _go():
async with app.db_sessionmaker() as s:
async with s.begin():
await set_setting(s, "metrics.retention.raw_days", 1)
s.add(PluginMetricHourly(source_module="test", resource_name=res,
metric_name="cpu_pct", bucket=two_days,
value_avg=10.0, value_max=10.0, sample_count=1))
s.add(PluginMetric(source_module="test", resource_name=res,
metric_name="cpu_pct", value=99.0, recorded_at=two_days))
try:
async with app.db_sessionmaker() as s:
return await metric_series.points(s, "test", res, "cpu_pct",
now - timedelta(days=3))
finally:
async with app.db_sessionmaker() as s:
async with s.begin():
await set_setting(s, "metrics.retention.raw_days", 7)
assert [v for _, v in asyncio.run(_go())] == [10.0]