diff --git a/tests/test_query_cross_dataset.py b/tests/test_query_cross_dataset.py index c9c7d20..6f4c410 100644 --- a/tests/test_query_cross_dataset.py +++ b/tests/test_query_cross_dataset.py @@ -238,3 +238,32 @@ def test_without_dry_run_still_raises(self, monkeypatch: MonkeyPatch) -> None: _mock_all(monkeypatch) with pytest.raises(ValueError, match="non consentita"): layer_query(datasets=["ds_a"], mode="sql", sql="SELECT * FROM unknown_table") + + +class TestMCPWrapperDryRun: + """Regressione: il wrapper MCP aggregate_ops.layer_query propaga dry_run. + + La PR #457 ha aggiunto dry_run al backend (domain.layer) e al tool MCP + (server.py) ma non al wrapper intermedio (aggregate_ops.layer_query) → + TypeError: layer_query() got an unexpected keyword argument 'dry_run' + sul percorso reale del tool. + """ + + def test_wrapper_propagates_dry_run(self, monkeypatch: MonkeyPatch) -> None: + """Il wrapper MCP accetta dry_run e lo passa al backend.""" + from toolkit.mcp.aggregate_ops import layer_query as mcp_layer_query + + received: dict[str, object] = {} + + def fake_backend(**kwargs: object) -> dict[str, object]: + received.update(kwargs) + return {"valid": True, "plan": "PLAN", "dry_run": True} + + monkeypatch.setattr("toolkit.mcp.aggregate_ops._layer_query_core", fake_backend) + + res = mcp_layer_query( + datasets=["ds_a"], layer="clean", mode="sql", sql="SELECT 1", dry_run=True + ) + + assert res["valid"] is True + assert received.get("dry_run") is True diff --git a/toolkit/mcp/aggregate_ops.py b/toolkit/mcp/aggregate_ops.py index 03809d6..a860470 100644 --- a/toolkit/mcp/aggregate_ops.py +++ b/toolkit/mcp/aggregate_ops.py @@ -52,6 +52,7 @@ def layer_query( sql: str | None = None, mart_index: int = 0, table: str | None = None, + dry_run: bool = False, ) -> dict[str, Any]: """Query unificata su layer RAW/CLEAN/MART. @@ -71,6 +72,7 @@ def layer_query( sql: Query SQL per mode=sql. mart_index: Indice tabella mart (solo pipeline mode). table: Nome tabella mart (es ``"mart_top_sa"``). + dry_run: Se True (mode=sql), valida lo scope SQL e fa EXPLAIN senza eseguire. Raises: ToolkitClientError: se parametri invalidi o file non trovato. @@ -97,6 +99,7 @@ def layer_query( sql=sql, mart_index=mart_index, table=table, + dry_run=dry_run, ) except ValueError as exc: raise ToolkitClientError(str(exc), code=ErrorCode.INVALID_PARAMS) from exc