Files
the-other-dude/backend/tests/test_restore_preview.py
Jason Staack 06a41ca9bf fix(lint): resolve all ruff lint errors
Add ruff config to exclude alembic E402, SQLAlchemy F821, and pre-existing
E501 line-length issues. Auto-fix 69 unused imports and 2 f-strings without
placeholders. Manually fix 8 unused variables. Apply ruff format to 127 files.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-14 22:17:50 -05:00

230 lines
7.9 KiB
Python

"""Tests for the preview-restore endpoint."""
import uuid
from unittest.mock import AsyncMock, patch, MagicMock
import pytest
class TestPreviewRestoreEndpointExists:
"""Verify the preview-restore route is registered on the config_backups router."""
def test_router_has_preview_restore_route(self):
from app.routers.config_backups import router
paths = [r.path for r in router.routes]
assert any("preview-restore" in p for p in paths), (
f"No preview-restore route found. Routes: {paths}"
)
def test_preview_restore_route_is_post(self):
from app.routers.config_backups import router
for route in router.routes:
if hasattr(route, "path") and "preview-restore" in route.path:
assert "POST" in route.methods, (
f"preview-restore route should be POST, got {route.methods}"
)
break
else:
pytest.fail("No preview-restore route found")
class TestPreviewRestoreFunction:
"""Test the preview_restore handler logic."""
@pytest.mark.asyncio
async def test_preview_returns_impact_analysis(self):
"""preview_restore should return diff, categories, warnings, validation."""
from app.routers.config_backups import preview_restore, RestoreRequest
tenant_id = uuid.uuid4()
device_id = uuid.uuid4()
current_export = "/ip address\nadd address=192.168.1.1/24 interface=ether1\n"
target_export = "/ip address\nadd address=10.0.0.1/24 interface=ether1\n"
mock_db = AsyncMock()
mock_user = MagicMock()
mock_request = MagicMock()
body = RestoreRequest(commit_sha="abc1234")
# Mock device query result
mock_device = MagicMock()
mock_device.ip_address = "192.168.88.1"
mock_device.encrypted_credentials_transit = "vault:v1:abc"
mock_device.encrypted_credentials = None
mock_device.tenant_id = tenant_id
mock_scalar = MagicMock()
mock_scalar.scalar_one_or_none.return_value = mock_device
mock_db.execute.return_value = mock_scalar
with (
patch(
"app.routers.config_backups._check_tenant_access",
new_callable=AsyncMock,
),
patch(
"app.routers.config_backups.limiter.enabled",
False,
),
patch(
"app.routers.config_backups.git_store.read_file",
return_value=target_export.encode(),
),
patch(
"app.routers.config_backups.backup_service.capture_export",
new_callable=AsyncMock,
return_value=current_export,
),
patch(
"app.routers.config_backups.decrypt_credentials_hybrid",
new_callable=AsyncMock,
return_value='{"username": "admin", "password": "pass"}',
),
patch(
"app.routers.config_backups.settings",
),
):
result = await preview_restore(
request=mock_request,
tenant_id=tenant_id,
device_id=device_id,
body=body,
db=mock_db,
current_user=mock_user,
)
assert "diff" in result
assert "categories" in result
assert "warnings" in result
assert "validation" in result
# Both exports have /ip address with different commands
assert isinstance(result["categories"], list)
assert isinstance(result["diff"], dict)
assert "added" in result["diff"]
assert "removed" in result["diff"]
@pytest.mark.asyncio
async def test_preview_falls_back_to_latest_backup_when_device_unreachable(self):
"""When live capture fails, preview should fall back to the latest backup."""
from app.routers.config_backups import preview_restore, RestoreRequest
tenant_id = uuid.uuid4()
device_id = uuid.uuid4()
current_export = "/ip address\nadd address=192.168.1.1/24 interface=ether1\n"
target_export = "/ip address\nadd address=10.0.0.1/24 interface=ether1\n"
mock_db = AsyncMock()
mock_user = MagicMock()
mock_request = MagicMock()
body = RestoreRequest(commit_sha="abc1234")
# Mock device query result
mock_device = MagicMock()
mock_device.ip_address = "192.168.88.1"
mock_device.encrypted_credentials_transit = "vault:v1:abc"
mock_device.encrypted_credentials = None
mock_device.tenant_id = tenant_id
# First call: device query, second call: latest backup query
mock_device_result = MagicMock()
mock_device_result.scalar_one_or_none.return_value = mock_device
mock_latest_run = MagicMock()
mock_latest_run.commit_sha = "latest123"
mock_backup_result = MagicMock()
mock_backup_result.scalar_one_or_none.return_value = mock_latest_run
mock_db.execute.side_effect = [mock_device_result, mock_backup_result]
def mock_read_file(tid, sha, did, filename):
if sha == "abc1234":
return target_export.encode()
elif sha == "latest123":
return current_export.encode()
return b""
with (
patch(
"app.routers.config_backups._check_tenant_access",
new_callable=AsyncMock,
),
patch(
"app.routers.config_backups.limiter.enabled",
False,
),
patch(
"app.routers.config_backups.git_store.read_file",
side_effect=mock_read_file,
),
patch(
"app.routers.config_backups.backup_service.capture_export",
new_callable=AsyncMock,
side_effect=ConnectionError("Device unreachable"),
),
patch(
"app.routers.config_backups.decrypt_credentials_hybrid",
new_callable=AsyncMock,
return_value='{"username": "admin", "password": "pass"}',
),
patch(
"app.routers.config_backups.settings",
),
):
result = await preview_restore(
request=mock_request,
tenant_id=tenant_id,
device_id=device_id,
body=body,
db=mock_db,
current_user=mock_user,
)
assert "diff" in result
assert "categories" in result
assert "warnings" in result
assert "validation" in result
@pytest.mark.asyncio
async def test_preview_404_when_backup_not_found(self):
"""preview_restore should return 404 when the target backup doesn't exist."""
from app.routers.config_backups import preview_restore, RestoreRequest
from fastapi import HTTPException
tenant_id = uuid.uuid4()
device_id = uuid.uuid4()
mock_db = AsyncMock()
mock_user = MagicMock()
mock_request = MagicMock()
body = RestoreRequest(commit_sha="nonexistent")
with (
patch(
"app.routers.config_backups._check_tenant_access",
new_callable=AsyncMock,
),
patch(
"app.routers.config_backups.limiter.enabled",
False,
),
patch(
"app.routers.config_backups.git_store.read_file",
side_effect=KeyError("not found"),
),
):
with pytest.raises(HTTPException) as exc_info:
await preview_restore(
request=mock_request,
tenant_id=tenant_id,
device_id=device_id,
body=body,
db=mock_db,
current_user=mock_user,
)
assert exc_info.value.status_code == 404