From eb76343d0482a13ee85bcdd499fc7ebc71917076 Mon Sep 17 00:00:00 2001 From: Jason Staack Date: Thu, 12 Mar 2026 22:32:40 -0500 Subject: [PATCH] feat(05-01): wire diff generation into snapshot subscriber - Add RETURNING id to snapshot INSERT for new_snapshot_id capture - Call generate_and_store_diff after successful commit (best-effort) - Outer try/except safety net ensures snapshot ack never blocked by diff - Update subscriber tests to mock diff service Co-Authored-By: Claude Opus 4.6 --- .../app/services/config_snapshot_subscriber.py | 16 ++++++++++++++-- backend/tests/test_config_snapshot_subscriber.py | 6 ++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/backend/app/services/config_snapshot_subscriber.py b/backend/app/services/config_snapshot_subscriber.py index 6bb82c6..b7ca23d 100644 --- a/backend/app/services/config_snapshot_subscriber.py +++ b/backend/app/services/config_snapshot_subscriber.py @@ -20,6 +20,7 @@ from sqlalchemy.exc import IntegrityError, OperationalError from app.config import settings from app.database import AdminAsyncSessionLocal +from app.services.config_diff_service import generate_and_store_diff from app.services.openbao_service import OpenBaoTransitService logger = logging.getLogger(__name__) @@ -134,12 +135,13 @@ async def handle_config_snapshot(msg) -> None: # --- INSERT new snapshot --- try: - await session.execute( + insert_result = await session.execute( text( "INSERT INTO router_config_snapshots " "(device_id, tenant_id, config_text, sha256_hash, collected_at) " "VALUES (CAST(:device_id AS uuid), CAST(:tenant_id AS uuid), " - ":config_text, :sha256_hash, :collected_at)" + ":config_text, :sha256_hash, :collected_at) " + "RETURNING id" ), { "device_id": device_id, @@ -149,6 +151,7 @@ async def handle_config_snapshot(msg) -> None: "collected_at": collected_at, }, ) + new_snapshot_id = insert_result.scalar_one() await session.commit() except IntegrityError: logger.warning( @@ -170,6 +173,15 @@ async def handle_config_snapshot(msg) -> None: await msg.nak() return + # --- Diff generation (best-effort) --- + try: + await generate_and_store_diff(device_id, tenant_id, str(new_snapshot_id), session) + except Exception as exc: + logger.warning( + "Diff generation failed for device %s (non-fatal): %s", + device_id, exc, + ) + logger.info( "Config snapshot stored for device %s tenant %s", device_id, diff --git a/backend/tests/test_config_snapshot_subscriber.py b/backend/tests/test_config_snapshot_subscriber.py index ef4af68..d8c0211 100644 --- a/backend/tests/test_config_snapshot_subscriber.py +++ b/backend/tests/test_config_snapshot_subscriber.py @@ -62,6 +62,9 @@ async def test_new_snapshot_encrypted_and_stored(): ), patch( "app.services.config_snapshot_subscriber.OpenBaoTransitService", return_value=mock_openbao, + ), patch( + "app.services.config_snapshot_subscriber.generate_and_store_diff", + new_callable=AsyncMock, ): await handle_config_snapshot(msg) @@ -248,6 +251,9 @@ async def test_first_snapshot_for_device_always_stored(): ), patch( "app.services.config_snapshot_subscriber.OpenBaoTransitService", return_value=mock_openbao, + ), patch( + "app.services.config_snapshot_subscriber.generate_and_store_diff", + new_callable=AsyncMock, ): await handle_config_snapshot(msg)