"""Tests for the AXL anti-pattern error-hint enhancement. Source: cucx-docs handoff (msg 003 in axl/agent-threads/cucx-prompt-suggestions/) — three recurring operator mistakes with cluster-side error messages that don't suggest the right path. Hints are surgical: only fire when both the error fragment AND the query trigger phrase match. """ import pytest from mcaxl.client import _augment_axl_error class TestNumplanFkdeviceHint: def test_fires_on_matching_error_and_query(self): err = "Column (fkdevice) not found in any table in the query (or SLV is undefined)." query = "SELECT name FROM numplan WHERE fkdevice = 'foo'" out = _augment_axl_error(err, query) assert err in out assert "devicenumplanmap" in out assert "M:N" in out def test_no_fire_on_error_alone(self): # Same error fragment but query doesn't mention numplan err = "Column (fkdevice) not found in any table in the query." query = "SELECT name FROM device WHERE fkdevice = 'foo'" assert _augment_axl_error(err, query) == err def test_no_fire_on_unrelated_error(self): err = "Some completely different error about a different column." query = "SELECT name FROM numplan" assert _augment_axl_error(err, query) == err def test_case_insensitive_query_match(self): # User's query in mixed case should still trigger err = "Column (fkdevice) not found in any table in the query." query = "SELECT name FROM NumPlan np WHERE np.fkdevice IS NOT NULL" out = _augment_axl_error(err, query) assert "devicenumplanmap" in out class TestSipDestinationHint: def test_fires_on_matching_error_and_query(self): err = "Table (sipdestination) is not in database." query = "SELECT * FROM sipdestination" out = _augment_axl_error(err, query) assert "sipdestinationgroup" in out assert "axl_list_tables" in out def test_no_fire_when_query_doesnt_reference_table(self): err = "Table (xyz) is not in database." query = "SELECT * FROM xyz" assert _augment_axl_error(err, query) == err class TestNoMatch: def test_unrelated_error_returns_unchanged(self): err = "Permission denied for user 'CCMSysUser'." query = "SELECT * FROM device" assert _augment_axl_error(err, query) == err def test_empty_error_returns_empty(self): assert _augment_axl_error("", "SELECT 1") == "" def test_identity_check_for_no_hint(self): """Caller compares identity to decide whether to wrap the original exception. Make sure no-hint path returns the literal input.""" err = "Random AXL error" query = "SELECT 1" out = _augment_axl_error(err, query) assert out is err # not just equal — same object class TestEndToEnd: """Confirm the augmentation propagates through execute_sql_query. Uses a mock that raises the AXL exception from inside zeep's call; the augmenter should wrap it as a RuntimeError with the hint appended. """ def test_execute_sql_query_wraps_with_hint(self): from unittest.mock import MagicMock from mcaxl.client import AxlClient from mcaxl.cache import AxlCache # Real cache, but in a temp location so tests don't pollute import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as td: cache = AxlCache(Path(td) / "test.sqlite", default_ttl=0, cluster_id="test") client = AxlClient(cache) # Bypass _ensure_connected by setting service directly mock_service = MagicMock() mock_service.executeSQLQuery.side_effect = RuntimeError( "Server raised fault: Column (fkdevice) not found in any table" ) client._service = mock_service client._connected_at = 0.0 with pytest.raises(RuntimeError, match="devicenumplanmap"): client.execute_sql_query( "SELECT name FROM numplan WHERE fkdevice IS NOT NULL" ) def test_execute_sql_query_passes_through_unrelated_error(self): from unittest.mock import MagicMock from mcaxl.client import AxlClient from mcaxl.cache import AxlCache import tempfile from pathlib import Path with tempfile.TemporaryDirectory() as td: cache = AxlCache(Path(td) / "test.sqlite", default_ttl=0, cluster_id="test") client = AxlClient(cache) mock_service = MagicMock() mock_service.executeSQLQuery.side_effect = RuntimeError( "Some unrelated cluster error" ) client._service = mock_service client._connected_at = 0.0 # Original exception type + message preserved when no hint applies with pytest.raises(RuntimeError, match="Some unrelated cluster error"): client.execute_sql_query("SELECT * FROM device")