"""Tests for device_grep — fuzzy device discovery by name/description. The "wait there are TWO of these?" finding shape from cucx-docs's ZetaFax-vs-RightFax discovery (msg 001 in axl/agent-threads/cucx-prompt-suggestions/). """ import pytest from mcaxl.route_plan import device_grep class FakeAxlClient: def __init__(self, rows): self._rows = rows self.queries = [] def execute_sql_query(self, sql): self.queries.append(sql) return {"row_count": len(self._rows), "rows": self._rows} def _make_row(name, description, class_name, device_type="SIP Trunk", pool="DP-1"): return { "name": name, "description": description, "class_name": class_name, "device_type": device_type, "pool_name": pool, } class TestDeviceGrepBasics: def test_groups_by_class(self): rows = [ _make_row("RightFax-Trunk", "RightFax inbound", "Trunk"), _make_row("ZetaFax-Trunk", "ZetaFax internal", "Trunk"), _make_row("SEPABCDFAXX01", "RightFax desk test", "Phone", "Cisco 8841"), _make_row("RightFax-RL", "RightFax route list", "Route List"), ] client = FakeAxlClient(rows) result = device_grep(client, "FAX") assert result["match_count"] == 4 assert set(result["groups"].keys()) == {"Trunk", "Phone", "Route List"} assert len(result["groups"]["Trunk"]) == 2 def test_class_filter_passed_to_sql(self): client = FakeAxlClient([]) device_grep(client, "FAX", classes=["Trunk", "Route List"]) # Both class names appear escaped in the SQL IN clause sql = client.queries[0] assert "tc.name IN" in sql assert "'Trunk'" in sql assert "'Route List'" in sql def test_no_class_filter_omits_in_clause(self): client = FakeAxlClient([]) device_grep(client, "FAX") assert "tc.name IN" not in client.queries[0] def test_empty_pattern_raises(self): client = FakeAxlClient([]) with pytest.raises(ValueError, match="non-empty"): device_grep(client, "") with pytest.raises(ValueError, match="non-empty"): device_grep(client, " ") def test_pattern_quote_escaped(self): client = FakeAxlClient([]) device_grep(client, "fake'; DROP TABLE device --") # SQL injection via pattern is escaped (doubled single quotes) assert "fake''" in client.queries[0] def test_class_filter_quote_escaped(self): client = FakeAxlClient([]) device_grep(client, "FAX", classes=["Phone'; DROP TABLE device --"]) assert "Phone''" in client.queries[0] def test_unknown_class_grouped_separately(self): # If tkclass enum doesn't resolve (LEFT JOIN miss), class_name is NULL rows = [ {"name": "WeirdDevice", "description": "?", "class_name": None, "device_type": None, "pool_name": None}, ] client = FakeAxlClient(rows) result = device_grep(client, "weird") assert "Unknown" in result["groups"] assert result["groups"]["Unknown"][0]["name"] == "WeirdDevice" def test_response_includes_filter_metadata(self): client = FakeAxlClient([]) result = device_grep(client, "FAX", classes=["Trunk"]) assert result["pattern"] == "FAX" assert result["classes_filter"] == ["Trunk"]