import json import pytest from app.runner.nodes import ( GovernedHttpExecutor, NodeExecutionError, NodeRegistry, RestrictedPythonExecutor, SqlExecuteExecutor, SqlQueryExecutor, ) class Rows: def __init__(self, rows=(), rowcount=0): self._rows = rows self.rowcount = rowcount def mappings(self): return self def all(self): return list(self._rows) class Connection: def __init__(self, result): self.result = result self.calls = [] def execute(self, statement, parameters): self.calls.append((str(statement), parameters)) return self.result class Manager: def __init__(self, result): self.connection = Connection(result) self.purposes = [] def connect(self, uid, purpose): manager = self class Context: def __enter__(self): manager.purposes.append((uid, purpose)) return manager.connection def __exit__(self, *_args): return False return Context() def test_sql_query_is_parameterized_read_only_and_bounded(): manager = Manager(Rows([{"id": 1}, {"id": 2}])) executor = SqlQueryExecutor(manager, max_rows=1) node = { "id": "read", "type": "sql.query", "data_source_uid": "source-1", "purpose": "read", "config": { "statement": "SELECT id FROM orders WHERE day = :day", "parameters": {"day": "2026-07-19"}, }, } result = executor.execute(node, {}) assert result == {"rows": [{"id": 1}], "row_count": 1, "truncated": True} assert manager.purposes == [("source-1", "dataflow_read")] assert manager.connection.calls == [ ("SELECT id FROM orders WHERE day = :day", {"day": "2026-07-19"}) ] def test_sql_parameters_resolve_only_declared_runtime_placeholders(): manager = Manager(Rows([{"id": 1}])) executor = SqlQueryExecutor(manager) node = { "id": "read", "type": "sql.query", "data_source_uid": "source-1", "purpose": "read", "config": { "statement": "SELECT id FROM orders WHERE day = :day", "parameters": {"day": "${parameters.day}"}, }, } executor.execute(node, {"day": "2026-07-19"}) assert manager.connection.calls[-1][1] == {"day": "2026-07-19"} with pytest.raises(NodeExecutionError, match="runtime parameter"): executor.execute(node, {}) @pytest.mark.parametrize( "statement", [ "DELETE FROM orders", "SELECT 1; DELETE FROM orders", "COPY orders TO PROGRAM 'id'", "WITH deleted AS (DELETE FROM orders RETURNING *) SELECT * FROM deleted", ], ) def test_sql_query_rejects_non_read_statements(statement): executor = SqlQueryExecutor(Manager(Rows())) node = { "id": "read", "type": "sql.query", "data_source_uid": "source-1", "purpose": "read", "config": {"statement": statement, "parameters": {}}, } with pytest.raises(NodeExecutionError, match="read-only"): executor.execute(node, {}) def test_sql_execute_requires_governed_write_and_idempotency(): manager = Manager(Rows(rowcount=3)) executor = SqlExecuteExecutor(manager) node = { "id": "write", "type": "sql.execute", "data_source_uid": "source-1", "purpose": "write", "config": { "statement": "UPDATE orders SET ready = :ready WHERE day = :day", "parameters": {"ready": True, "day": "2026-07-19"}, }, "idempotency": { "strategy": "deduplication_key", "key": "orders:2026-07-19", }, } result = executor.execute(node, {}, write_authorized=True) assert result == {"affected_rows": 3, "commit_outcome": "committed"} assert manager.purposes == [("source-1", "dataflow_write")] with pytest.raises(NodeExecutionError, match="authorization"): executor.execute(node, {}, write_authorized=False) with pytest.raises(NodeExecutionError, match="idempotency"): executor.execute({**node, "idempotency": {}}, {}, write_authorized=True) def test_sql_execute_never_converts_unknown_commit_into_a_retryable_failure(): class UnknownManager: def connect(self, _uid, purpose): assert purpose == "dataflow_write" class Context: def __enter__(self): return Connection(Rows(rowcount=1)) def __exit__(self, *_args): error = RuntimeError("connection lost during commit") error.commit_outcome = "unknown" raise error return Context() node = { "id": "write", "type": "sql.execute", "data_source_uid": "source-1", "purpose": "write", "config": { "statement": "UPDATE orders SET ready = :ready", "parameters": {"ready": True}, }, "idempotency": { "strategy": "deduplication_key", "key": "orders:2026-07-19", }, } with pytest.raises(NodeExecutionError) as captured: SqlExecuteExecutor(UnknownManager()).execute( node, {}, write_authorized=True, ) assert captured.value.commit_outcome == "unknown" @pytest.mark.parametrize( "statement", [ "DROP TABLE orders", "GRANT ALL ON orders TO public", "UPDATE orders SET ready = true; DELETE FROM orders", "SELECT * FROM orders", ], ) def test_sql_execute_accepts_dml_only(statement): node = { "id": "write", "type": "sql.execute", "data_source_uid": "source-1", "purpose": "write", "config": {"statement": statement, "parameters": {}}, "idempotency": { "strategy": "deduplication_key", "key": "orders:2026-07-19", }, } with pytest.raises(NodeExecutionError, match="DML"): SqlExecuteExecutor(Manager(Rows(rowcount=1))).execute( node, {}, write_authorized=True, ) def test_restricted_python_executes_only_registered_handlers(): executor = RestrictedPythonExecutor( {"normalize_orders": lambda values: {"count": len(values["orders"])}} ) node = { "id": "normalize", "type": "python", "config": { "handler": "normalize_orders", "arguments": {"orders": [1, 2]}, }, } assert executor.execute(node, {}) == {"count": 2} for config in ( {"source": "import os"}, {"handler": "unknown", "arguments": {}}, ): with pytest.raises(NodeExecutionError): executor.execute({**node, "config": config}, {}) def test_restricted_python_enforces_timeout_and_no_network(): import socket import time def use_network(_arguments): socket.socket() def run_forever(_arguments): time.sleep(1) node = { "id": "restricted", "type": "python", "config": {"handler": "blocked", "arguments": {}}, } with pytest.raises(NodeExecutionError, match="network"): RestrictedPythonExecutor( {"blocked": use_network}, timeout_seconds=1, ).execute(node, {}) with pytest.raises(NodeExecutionError, match="time limit"): RestrictedPythonExecutor( {"blocked": run_forever}, timeout_seconds=0.05, ).execute(node, {}) def test_http_node_uses_allowlist_and_never_accepts_inline_authorization(): calls = [] class Session: def request(self, method, url, **kwargs): calls.append((method, url, kwargs)) class Response: status_code = 200 content = b'{"ok":true}' headers = {"content-type": "application/json"} def json(self): return json.loads(self.content) return Response() executor = GovernedHttpExecutor( allowed_hosts={"api.internal.example"}, session=Session(), ) node = { "id": "notify", "type": "http", "config": { "method": "POST", "url": "https://api.internal.example/events", "json": {"state": "ready"}, }, } assert executor.execute(node, {})["body"] == {"ok": True} for config in ( {**node["config"], "url": "http://127.0.0.1/admin"}, {**node["config"], "headers": {"Authorization": "Bearer secret"}}, {**node["config"], "url": "https://user:pass@api.internal.example/events"}, ): with pytest.raises(NodeExecutionError): executor.execute({**node, "config": config}, {}) def test_node_registry_fails_closed_for_unregistered_types(): registry = NodeRegistry({"sql.query": object()}) with pytest.raises(NodeExecutionError, match="not registered"): registry.execute({"type": "notify"}, {})