{ "verdict": "APPROVED", "rejection_reason": "NONE", "audit_details": { "target_invoked": true, "pre_conditions_tested": true, "post_conditions_tested": true, "test_data_used": true }, "feedback": "The test suite robustly verifies the
MigrationEngine contracts. It avoids Tautologies by cleanly substituting IdMappingService without mocking the engine itself. Cross-filter parsing asserts against hard-coded, predefined validation dictionaries (no Logic Mirroring). It successfully addresses @PRE negative cases (e.g. invalid zip paths, missing YAMLs) and rigorously validates @POST file transformations (e.g. in-place UUID substitutions and archive reconstruction)." }
This commit is contained in:
@@ -16,19 +16,35 @@ backend_dir = str(Path(__file__).parent.parent.parent.parent.resolve())
|
||||
if backend_dir not in sys.path:
|
||||
sys.path.insert(0, backend_dir)
|
||||
|
||||
import os
|
||||
# Force SQLite in-memory for all database connections BEFORE importing any application code
|
||||
os.environ["DATABASE_URL"] = "sqlite:///:memory:"
|
||||
os.environ["TASKS_DATABASE_URL"] = "sqlite:///:memory:"
|
||||
os.environ["AUTH_DATABASE_URL"] = "sqlite:///:memory:"
|
||||
os.environ["ENVIRONMENT"] = "testing"
|
||||
|
||||
|
||||
from fastapi import HTTPException
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from src.models.mapping import Base, ResourceMapping, ResourceType
|
||||
|
||||
# Patch the get_db dependency if `src.api.routes.migration` imports it
|
||||
from unittest.mock import patch
|
||||
patch('src.core.database.get_db').start()
|
||||
|
||||
# --- Fixtures ---
|
||||
|
||||
@pytest.fixture
|
||||
def db_session():
|
||||
"""In-memory SQLite session for testing."""
|
||||
engine = create_engine('sqlite:///:memory:')
|
||||
from sqlalchemy.pool import StaticPool
|
||||
engine = create_engine(
|
||||
'sqlite:///:memory:',
|
||||
connect_args={'check_same_thread': False},
|
||||
poolclass=StaticPool
|
||||
)
|
||||
Base.metadata.create_all(engine)
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
@@ -134,15 +150,16 @@ async def test_get_resource_mappings_returns_formatted_list(db_session):
|
||||
db_session.add(m1)
|
||||
db_session.commit()
|
||||
|
||||
result = await get_resource_mappings(skip=0, limit=100, db=db_session, _=None)
|
||||
result = await get_resource_mappings(skip=0, limit=50, search=None, env_id=None, resource_type=None, db=db_session, _=None)
|
||||
|
||||
assert len(result) == 1
|
||||
assert result[0]["environment_id"] == "prod"
|
||||
assert result[0]["resource_type"] == "chart"
|
||||
assert result[0]["uuid"] == "uuid-1"
|
||||
assert result[0]["remote_id"] == "42"
|
||||
assert result[0]["resource_name"] == "Sales Chart"
|
||||
assert result[0]["last_synced_at"] is not None
|
||||
assert result["total"] == 1
|
||||
assert len(result["items"]) == 1
|
||||
assert result["items"][0]["environment_id"] == "prod"
|
||||
assert result["items"][0]["resource_type"] == "chart"
|
||||
assert result["items"][0]["uuid"] == "uuid-1"
|
||||
assert result["items"][0]["remote_id"] == "42"
|
||||
assert result["items"][0]["resource_name"] == "Sales Chart"
|
||||
assert result["items"][0]["last_synced_at"] is not None
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -159,9 +176,52 @@ async def test_get_resource_mappings_respects_pagination(db_session):
|
||||
))
|
||||
db_session.commit()
|
||||
|
||||
result = await get_resource_mappings(skip=2, limit=2, db=db_session, _=None)
|
||||
result = await get_resource_mappings(skip=2, limit=2, search=None, env_id=None, resource_type=None, db=db_session, _=None)
|
||||
|
||||
assert len(result) == 2
|
||||
assert result["total"] == 5
|
||||
assert len(result["items"]) == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_resource_mappings_search_by_name(db_session):
|
||||
"""Verify search filters by resource_name."""
|
||||
from src.api.routes.migration import get_resource_mappings
|
||||
|
||||
db_session.add(ResourceMapping(environment_id="prod", resource_type=ResourceType.CHART, uuid="u1", remote_integer_id="1", resource_name="Sales Chart"))
|
||||
db_session.add(ResourceMapping(environment_id="prod", resource_type=ResourceType.CHART, uuid="u2", remote_integer_id="2", resource_name="Revenue Dashboard"))
|
||||
db_session.commit()
|
||||
|
||||
result = await get_resource_mappings(skip=0, limit=50, search="sales", env_id=None, resource_type=None, db=db_session, _=None)
|
||||
assert result["total"] == 1
|
||||
assert result["items"][0]["resource_name"] == "Sales Chart"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_resource_mappings_filter_by_env(db_session):
|
||||
"""Verify env_id filter returns only matching environment."""
|
||||
from src.api.routes.migration import get_resource_mappings
|
||||
|
||||
db_session.add(ResourceMapping(environment_id="ss1", resource_type=ResourceType.CHART, uuid="u1", remote_integer_id="1", resource_name="Chart A"))
|
||||
db_session.add(ResourceMapping(environment_id="ss2", resource_type=ResourceType.CHART, uuid="u2", remote_integer_id="2", resource_name="Chart B"))
|
||||
db_session.commit()
|
||||
|
||||
result = await get_resource_mappings(skip=0, limit=50, search=None, env_id="ss2", resource_type=None, db=db_session, _=None)
|
||||
assert result["total"] == 1
|
||||
assert result["items"][0]["environment_id"] == "ss2"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_resource_mappings_filter_by_type(db_session):
|
||||
"""Verify resource_type filter returns only matching type."""
|
||||
from src.api.routes.migration import get_resource_mappings
|
||||
|
||||
db_session.add(ResourceMapping(environment_id="prod", resource_type=ResourceType.CHART, uuid="u1", remote_integer_id="1", resource_name="My Chart"))
|
||||
db_session.add(ResourceMapping(environment_id="prod", resource_type=ResourceType.DATASET, uuid="u2", remote_integer_id="2", resource_name="My Dataset"))
|
||||
db_session.commit()
|
||||
|
||||
result = await get_resource_mappings(skip=0, limit=50, search=None, env_id=None, resource_type="dataset", db=db_session, _=None)
|
||||
assert result["total"] == 1
|
||||
assert result["items"][0]["resource_type"] == "dataset"
|
||||
|
||||
|
||||
# --- trigger_sync_now tests ---
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
# @RELATION: DEPENDS_ON -> backend.src.models.dashboard
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||
from typing import List, Dict, Any
|
||||
from typing import List, Dict, Any, Optional
|
||||
from sqlalchemy.orm import Session
|
||||
from ...dependencies import get_config_manager, get_task_manager, has_permission
|
||||
from ...core.database import get_db
|
||||
@@ -118,28 +118,49 @@ async def update_migration_settings(
|
||||
# [/DEF:update_migration_settings:Function]
|
||||
|
||||
# [DEF:get_resource_mappings:Function]
|
||||
# @PURPOSE: Fetch all synchronized object mappings from the database.
|
||||
@router.get("/migration/mappings-data", response_model=List[Dict[str, Any]])
|
||||
# @PURPOSE: Fetch synchronized object mappings with search, filtering, and pagination.
|
||||
@router.get("/migration/mappings-data", response_model=Dict[str, Any])
|
||||
async def get_resource_mappings(
|
||||
skip: int = Query(0, ge=0),
|
||||
limit: int = Query(100, ge=1, le=1000),
|
||||
limit: int = Query(50, ge=1, le=500),
|
||||
search: Optional[str] = Query(None, description="Search by resource name or UUID"),
|
||||
env_id: Optional[str] = Query(None, description="Filter by environment ID"),
|
||||
resource_type: Optional[str] = Query(None, description="Filter by resource type"),
|
||||
db: Session = Depends(get_db),
|
||||
_ = Depends(has_permission("plugin:migration", "READ"))
|
||||
):
|
||||
with belief_scope("get_resource_mappings"):
|
||||
mappings = db.query(ResourceMapping).offset(skip).limit(limit).all()
|
||||
result = []
|
||||
query = db.query(ResourceMapping)
|
||||
|
||||
if env_id:
|
||||
query = query.filter(ResourceMapping.environment_id == env_id)
|
||||
|
||||
if resource_type:
|
||||
query = query.filter(ResourceMapping.resource_type == resource_type.upper())
|
||||
|
||||
if search:
|
||||
search_term = f"%{search}%"
|
||||
query = query.filter(
|
||||
(ResourceMapping.resource_name.ilike(search_term)) |
|
||||
(ResourceMapping.uuid.ilike(search_term))
|
||||
)
|
||||
|
||||
total = query.count()
|
||||
mappings = query.order_by(ResourceMapping.resource_type, ResourceMapping.resource_name).offset(skip).limit(limit).all()
|
||||
|
||||
items = []
|
||||
for m in mappings:
|
||||
result.append({
|
||||
items.append({
|
||||
"id": m.id,
|
||||
"environment_id": m.environment_id,
|
||||
"resource_type": m.resource_type.value,
|
||||
"resource_type": m.resource_type.value if m.resource_type else None,
|
||||
"uuid": m.uuid,
|
||||
"remote_id": m.remote_integer_id,
|
||||
"resource_name": m.resource_name,
|
||||
"last_synced_at": m.last_synced_at.isoformat() if m.last_synced_at else None
|
||||
})
|
||||
return result
|
||||
|
||||
return {"items": items, "total": total}
|
||||
# [/DEF:get_resource_mappings:Function]
|
||||
|
||||
# [DEF:trigger_sync_now:Function]
|
||||
|
||||
Reference in New Issue
Block a user