"""Realized cost-optimization savings: tracker hook + leadership rollup. The efficiency engine reports ESTIMATED savings; this closes the loop to REALIZED. Covers: the audit hook only records FLAGGED servers, the baseline snapshot from cost_facts, and the read-time realized computation (destroy → full baseline; open opportunity for un-acted flagged servers). """ from __future__ import annotations import uuid from datetime import UTC, datetime, timedelta from decimal import Decimal import pytest from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.audit.models import AuditLog from app.domains.cmdb import savings_service from app.domains.cmdb.models import ( CmdbCostFact, CmdbEfficiencySavingsEvent, CmdbServer, CmdbServerEfficiency, ) from app.domains.cmdb.savings_tracker import _on_audit_log from app.integrations.models import Integration async def _integration(db): integ = Integration( id=uuid.uuid4(), kind="cloud", provider="aws", name="aws-s", slug=f"aws-{uuid.uuid4().hex[:6]}", enabled=True, is_primary=False, capabilities=["servers"], ) db.add(integ) await db.flush() return integ async def _server(db, integ, **kw): now = datetime.now(UTC) s = CmdbServer( id=uuid.uuid4(), instance_id=kw.pop("instance_id", f"i-{uuid.uuid4().hex[:10]}"), name="srv", status="running", region="ap-southeast-1", integration_id=str(integ.id), first_seen_at=now - timedelta(days=100), last_synced_at=now, **kw, ) db.add(s) await db.flush() return s async def _eff(db, integ, srv, verdict, savings=None): e = CmdbServerEfficiency( id=uuid.uuid4(), server_id=str(srv.id), integration_id=str(integ.id), verdict=verdict, recommendation="stop" if verdict == "idle" else "keep", estimated_monthly_savings_usd=Decimal(savings) if savings else None, computed_at=datetime.now(UTC), ) db.add(e) await db.flush() return e async def _cost(db, integ, resource_id, amount, day_offset, *, anchor=None): """Seed a cost fact ``day_offset`` days before ``anchor``. ``anchor`` MUST be a UTC date. ``CmdbCostFact.date`` is a UTC-anchored column and every consumer windows it against ``datetime.now(UTC).date()`` (see ``savings_service._post_action_monthly`` and the note at ``cost_service.py`` "``date.today()`` would be off by a day"). Seeding with process-local ``date.today()`` silently shifts every fact by ±1 day whenever the local date differs from the UTC date — i.e. 00:00–08:00 in CST (local = UTC+1d) and afternoons in the Americas (local = UTC−1d) — which drifts the facts out of the window under test. """ db.add(CmdbCostFact( fact_key=uuid.uuid4().hex, date=(anchor or datetime.now(UTC).date()) - timedelta(days=day_offset), integration_id=str(integ.id), resource_id=resource_id, resource_type="Instance", service="AmazonEC2", amount_usd=Decimal(str(amount)), currency="USD", )) await db.flush() def _audit(action, server_id, actor=None): return AuditLog( action=action, status="success", resource_type="cmdb_server", resource_id=str(server_id), actor_user_id=actor, ) @pytest.mark.asyncio async def test_hook_records_flagged_destroy(db_session: AsyncSession): integ = await _integration(db_session) srv = await _server(db_session, integ) await _eff(db_session, integ, srv, "idle", savings=200) # 30 days of $10/day cost → ~$300 trailing baseline. for d in range(1, 31): await _cost(db_session, integ, srv.instance_id, 10, d) await _on_audit_log(_audit("cmdb.server.destroy", srv.id), db_session) ev = ( await db_session.execute( select(CmdbEfficiencySavingsEvent).where( CmdbEfficiencySavingsEvent.server_id == str(srv.id), ) ) ).scalar_one() assert ev.method == "destroy" assert ev.verdict_at_action == "idle" assert ev.monthly_cost_baseline_usd is not None assert float(ev.monthly_cost_baseline_usd) == 300.0 @pytest.mark.asyncio async def test_hook_skips_unflagged(db_session: AsyncSession): integ = await _integration(db_session) srv = await _server(db_session, integ) await _eff(db_session, integ, srv, "healthy") for d in range(1, 31): await _cost(db_session, integ, srv.instance_id, 10, d) await _on_audit_log(_audit("cmdb.server.destroy", srv.id), db_session) ev = ( await db_session.execute( select(CmdbEfficiencySavingsEvent).where( CmdbEfficiencySavingsEvent.server_id == str(srv.id), ) ) ).scalar_one_or_none() assert ev is None # not flagged → not counted @pytest.mark.asyncio async def test_outcomes_realized_and_opportunity(db_session: AsyncSession): integ = await _integration(db_session) now = datetime.now(UTC) # (1) A flagged + destroyed server → realized. gone = await _server(db_session, integ) await _eff(db_session, integ, gone, "idle", savings=250) for d in range(1, 31): await _cost(db_session, integ, gone.instance_id, 10, d, anchor=now.date()) await _on_audit_log(_audit("cmdb.server.destroy", gone.id), db_session) # No cost lines AFTER the action (destroyed) → realized ≈ baseline. # (2) A flagged but NOT-acted server → open opportunity, not realized. live = await _server(db_session, integ) await _eff(db_session, integ, live, "oversized", savings=80) await db_session.flush() out = await savings_service.savings_outcomes(db_session, now=now) assert out["realized"]["monthly_usd"] == 300.0 assert out["realized"]["annualized_usd"] == 3600.0 assert out["realized"]["events"] == 1 methods = {r["key"]: r["monthly_usd"] for r in out["by_method"]} assert methods.get("destroy") == 300.0 # The un-acted oversized server shows as still-open opportunity. assert out["open_opportunity"]["monthly_usd"] == 80.0 assert out["open_opportunity"]["servers"] == 1 async def _event(db, integ, srv, *, method, acted_at, baseline, verdict="oversized"): ev = CmdbEfficiencySavingsEvent( id=uuid.uuid4(), server_id=str(srv.id), instance_id=srv.instance_id, integration_id=str(integ.id), method=method, source="console", verdict_at_action=verdict, monthly_cost_baseline_usd=Decimal(str(baseline)), cost_currency="USD", acted_at=acted_at, ) db.add(ev) await db.flush() return ev @pytest.mark.asyncio async def test_downsize_not_credited_before_settled_window(db_session: AsyncSession): """A downsize acted on ~now has no settled post-action facts yet — cloud billing lags ~T-2. Crediting (baseline − ~0) would spike realized to the full baseline (~2× the true saving). It must NOT be credited yet.""" integ = await _integration(db_session) srv = await _server(db_session, integ) now = datetime.now(UTC) await _event( db_session, integ, srv, method="downsize", acted_at=now - timedelta(days=1), baseline=200, ) out = await savings_service.savings_outcomes(db_session, now=now) assert out["realized"]["monthly_usd"] == 0.0 assert out["realized"]["events"] == 0 @pytest.mark.asyncio async def test_downsize_credited_after_settled_window(db_session: AsyncSession): """Once enough SETTLED post-action days exist, a downsize credits the real (baseline − measured post) saving, and the trailing billing-lag days are excluded from the measurement.""" integ = await _integration(db_session) srv = await _server(db_session, integ) now = datetime.now(UTC) await _event( db_session, integ, srv, method="downsize", acted_at=now - timedelta(days=20), baseline=300, ) # Anchor the seeded facts on the SAME UTC clock reading that produced # ``acted_at``, and inject that same instant into the service below, so # the fact dates line up exactly with the settled window it derives — no # residual dependence on the wall clock between seeding and reading. anchor = now.date() # Settled post-window is (acted, now-2d]; add $5/day across those 18 days # → daily $5 → $150/mo post → realized 300 − 150 = 150. for d in range(2, 20): await _cost(db_session, integ, srv.instance_id, 5, d, anchor=anchor) # A big fact inside the billing-lag window (now-1d) must be EXCLUDED and # not distort the measurement. await _cost(db_session, integ, srv.instance_id, 1000, 1, anchor=anchor) out = await savings_service.savings_outcomes(db_session, now=now) assert out["realized"]["monthly_usd"] == 150.0 assert out["realized"]["events"] == 1 @pytest.mark.asyncio async def test_outcomes_clock_is_injectable_and_drift_free( db_session: AsyncSession, ): """``savings_outcomes(now=...)`` derives EVERY window from the injected instant — the settled post-action window, the ``since_days`` cut-off and the trend bucket — so the result is a pure function of the seeded facts. Regression guard for the whole drift class: the service used to read ``datetime.now(UTC)`` internally, which (a) made this assertion depend on wall-clock/TZ alignment with the seeded fact dates and (b) left a real sub-second race where a UTC date rollover between a caller capturing ``now`` and the service reading its own clock shifted the window. The facts here are anchored to a FIXED historical instant, so a wall-clock fallback cannot possibly reproduce the exact credit asserted below. """ integ = await _integration(db_session) srv = await _server(db_session, integ) # Fixed instant, deliberately far in the past relative to any wall clock. frozen = datetime(2026, 3, 15, 12, 0, tzinfo=UTC) await _event( db_session, integ, srv, method="downsize", acted_at=frozen - timedelta(days=20), baseline=300, ) # Settled window is (acted, frozen-2d] → 18 days × $5 = $90 → $5/day → # $150/mo post cost → realized = 300 − 150 = 150. for d in range(2, 20): await _cost(db_session, integ, srv.instance_id, 5, d, anchor=frozen.date()) # Inside the billing-lag window → excluded from the settled measurement. # (Under a wall-clock fallback this fact lands INSIDE the derived window # and swamps it, which is precisely why the exact-value assert below is # only satisfiable via the injected clock.) await _cost(db_session, integ, srv.instance_id, 1000, 1, anchor=frozen.date()) out = await savings_service.savings_outcomes(db_session, now=frozen) assert out["realized"]["monthly_usd"] == 150.0 assert out["realized"]["annualized_usd"] == 1800.0 assert out["realized"]["events"] == 1 # Trend bucket comes from the (fixed) acted_at, not from any live clock. assert [r["month"] for r in out["trend"]] == ["2026-02"] # ``since_days`` is measured off the SAME injected instant: a 30d window # still contains an action 20 days before ``frozen``... windowed = await savings_service.savings_outcomes( db_session, since_days=30, now=frozen, ) assert windowed["realized"]["events"] == 1 # ...and a 10d window excludes it. tight = await savings_service.savings_outcomes( db_session, since_days=10, now=frozen, ) assert tight["realized"]["events"] == 0 # Same data read WITHOUT injection falls back to the wall clock and # cannot land on the injected result — proof the parameter is what drives # the window, not incidental alignment. fallback = await savings_service.savings_outcomes(db_session) assert fallback["realized"]["monthly_usd"] != 150.0 @pytest.mark.asyncio async def test_open_opportunity_excludes_prior_window_acted_server( db_session: AsyncSession, ): """A server acted on BEFORE the ``since_days`` window but still carrying a live actionable verdict must NOT be re-counted as an open opportunity — the exclusion set spans ALL acted servers, not just windowed ones.""" integ = await _integration(db_session) srv = await _server(db_session, integ) # Still flagged idle/oversized in the current efficiency table... await _eff(db_session, integ, srv, "oversized", savings=200) now = datetime.now(UTC) # ...but it was already acted on 100 days ago (outside a 30d window). await _event( db_session, integ, srv, method="downsize", acted_at=now - timedelta(days=100), baseline=200, ) out = await savings_service.savings_outcomes( db_session, since_days=30, now=now, ) # Outside the window → not in the realized rollup... assert out["realized"]["events"] == 0 # ...and NOT double-counted as a fresh open opportunity either. assert out["open_opportunity"]["servers"] == 0 assert out["open_opportunity"]["monthly_usd"] == 0.0 @pytest.mark.asyncio async def test_hook_snapshots_inherited_scope_owner(db_session: AsyncSession): """A flagged server with NO stored owner snapshots the owner (and department) it inherits from its cloud subscription — even though the destroy path soft-deleted the row before the hook fired.""" from app.domains.cmdb.models import CmdbCloudScope from app.identity.person_models import IdentityOrgMembership, IdentityPerson integ = await _integration(db_session) owner = IdentityPerson( id=uuid.uuid4(), work_email=f"{uuid.uuid4().hex[:8]}@pingcap.com", display_name="ScopeOwner", employment_status="active", source_system="successfactors", hris_person_id=f"sf-{uuid.uuid4().hex[:8]}", ) db_session.add(owner) await db_session.flush() db_session.add(IdentityOrgMembership( id=uuid.uuid4(), person_id=owner.id, org_node_source_id=f"node-{uuid.uuid4().hex[:8]}", org_node_type="department", org_node_name="Cloud", org_path="PingCAP / Engineering / Cloud", is_primary=True, membership_status="active", )) scope = CmdbCloudScope( id=uuid.uuid4(), integration_id=str(integ.id), scope_type="aws_account", external_id=f"acct-{uuid.uuid4().hex[:8]}", display_name="sub", owner_person_id=str(owner.id), ) db_session.add(scope) await db_session.flush() srv = await _server(db_session, integ) await _eff(db_session, integ, srv, "idle", savings=50) anchor = datetime.now(UTC).date() # UTC-anchored column — see _cost() for d in range(1, 31): db_session.add(CmdbCostFact( fact_key=uuid.uuid4().hex, date=anchor - timedelta(days=d), integration_id=str(integ.id), scope_id=str(scope.id), resource_id=srv.instance_id, resource_type="Instance", service="AmazonEC2", amount_usd=Decimal("10"), currency="USD", )) # Destroy soft-deletes BEFORE the audit hook runs — mirror that. srv.deleted_at = datetime.now(UTC) await db_session.flush() await _on_audit_log(_audit("cmdb.server.destroy", srv.id), db_session) ev = ( await db_session.execute( select(CmdbEfficiencySavingsEvent).where( CmdbEfficiencySavingsEvent.server_id == str(srv.id), ) ) ).scalar_one() assert ev.owner_person_id == str(owner.id) assert ev.owner_department == "Cloud"