[{'Text': 'Edited point-topic-mcp/tests/test_gbs_query.py:\n\n```diff\n@@ -1,0 +1,163 @@\n+"""Tests for read_gbs tool.\n+\n+Mocks mongodb_utils to test validation, EJSON parsing, and error paths\n+without requiring a real MongoDB connection.\n+"""\n+\n+import json\n+from unittest.mock import patch\n+\n+import pytest\n+\n+\n+@pytest.fixture\n+def gbs_env():\n+    """Ensure PT_RESEARCH_DATABASE_URI looks set so tools register."""\n+    with patch.dict("os.environ", {"PT_RESEARCH_DATABASE_URI": "mongodb://localhost:27017/t"}, clear=False):\n+        import importlib\n+        import point_topic_mcp.tools.gbs_query_tools as gbs\n+        importlib.reload(gbs)\n+        yield gbs\n+\n+\n+def test_invalid_json(gbs_env):\n+    """Returns error for unparseable JSON."""\n+    gbs = gbs_env\n+    result = gbs.read_gbs(pipeline_json="not json")\n+    assert "Error" in result\n+    assert "Invalid JSON" in result\n+\n+\n+def test_no_pipeline(gbs_env):\n+    """Returns error when no pipeline provided."""\n+    gbs = gbs_env\n+    result = gbs.read_gbs()\n+    assert "Error" in result\n+    assert "Provide" in result\n+\n+\n+def test_write_stage_rejected(gbs_env):\n+    """$out and $merge are rejected as write stages."""\n+    gbs = gbs_env\n+    result = gbs.read_gbs(pipeline_json=\'[{"$match": {}}, {"$out": "test"}]\')\n+    assert "Error" in result\n+    assert "write-only" in result\n+\n+    result = gbs.read_gbs(pipeline_json=\'[{"$match": {}}, {"$merge": {}}]\')\n+    assert "Error" in result\n+    assert "write-only" in result\n+\n+\n+def test_not_a_list(gbs_env):\n+    """Pipeline must be a list."""\n+    gbs = gbs_env\n+    result = gbs.read_gbs(pipeline_json=\'"not a list"\')\n+    assert "Error" in result\n+    assert "must be a JSON array" in result\n+\n+    result = gbs.read_gbs(pipeline_json=\'{}\')\n+    assert "Error" in result\n+    assert "must be a JSON array" in result\n+\n+\n+def test_empty_pipeline_stage(gbs_env):\n+    """Empty stage objects are rejected."""\n+    gbs = gbs_env\n+    result = gbs.read_gbs(pipeline_json=\'[{}]\')\n+    assert "Error" in result\n+    assert "empty" in result\n+\n+\n+def test_successful_query(gbs_env):\n+    """Valid pipeline runs and returns formatted JSON."""\n+    gbs = gbs_env\n+    pipeline = \'{"$match": {"country": "United Kingdom"}}, {"$limit": 1}\'\n+    mock_result = [{"_id": {"$oid": "abc123"}, "name": "TestOp", "country": "United Kingdom"}]\n+\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=mock_result):\n+        result = gbs.read_gbs(pipeline_json=f\'[{pipeline}]\')\n+        parsed = json.loads(result)\n+        assert len(parsed) == 1\n+        assert parsed[0]["name"] == "TestOp"\n+        assert parsed[0]["country"] == "United Kingdom"\n+\n+\n+def test_empty_result(gbs_env):\n+    """Empty result set returns helpful message, not error."""\n+    gbs = gbs_env\n+\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=[]):\n+        result = gbs.read_gbs(pipeline_json=\'[{"$match": {"name": "NoSuchOp"}}]\')\n+        assert "no documents matched" in result\n+\n+\n+def test_collection_detection(gbs_env):\n+    """Auto-detects the correct collection from $match keys."""\n+    gbs = gbs_env\n+\n+    # GlobalVariables collection detected\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=[{"currentStatsPeriod": "2026Q1"}]) as mock:\n+        result = gbs.read_gbs(pipeline_json=\'[{"$match": {"isCurrent": true}}]\')\n+        call_args = mock.call_args\n+        assert call_args is not None\n+        _, kwargs = call_args\n+        assert kwargs.get("collection") == "GlobalVariables"\n+\n+    # Statistics collection detected\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=[]) as mock:\n+        result = gbs.read_gbs(pipeline_json=\'[{"$match": {"period": "2026Q1", "type": "broadband"}}]\')\n+        call_args = mock.call_args\n+        _, kwargs = call_args\n+        assert kwargs.get("collection") == "Statistics"\n+\n+    # Operator collection detected\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=[]) as mock:\n+        result = gbs.read_gbs(pipeline_json=\'[{"$match": {"name": "BT Group", "country": "United Kingdom"}}]\')\n+        call_args = mock.call_args\n+        _, kwargs = call_args\n+        assert kwargs.get("collection") == "Operator"\n+\n+    # Sources collection detected\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=[]) as mock:\n+        result = gbs.read_gbs(pipeline_json=\'[{"$match": {"year": 2026, "quarter": 1}}]\')\n+        call_args = mock.call_args\n+        _, kwargs = call_args\n+        assert kwargs.get("collection") == "Sources"\n+\n+\n+def test_ejson_in_pipeline(gbs_env):\n+    """EJSON syntax ($oid, $date) is accepted and passed through."""\n+    gbs = gbs_env\n+    pipeline = \'[{"$match": {"operatorId": {"$oid": "662ff7a3f9d5751ffbf300f6"}, "period": "2026Q1"}}]\'\n+    mock_result = [{"tech": "FTTP", "subscribers": 316000}]\n+\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=mock_result) as mock:\n+        result = gbs.read_gbs(pipeline_json=pipeline)\n+        parsed = json.loads(result)\n+        assert parsed[0]["subscribers"] == 316000\n+\n+        # Verify EJSON $oid was passed through in the pipeline\n+        call_args = mock.call_args\n+        assert call_args is not None\n+        pipe_arg = call_args[0][0]\n+        # The pipeline should contain $oid\n+        assert any("$oid" in json.dumps(s) for s in pipe_arg)\n+\n+\n+def test_double_underscore_field(gbs_env):\n+    """Fields like __PERIOD__ are just passed through as-is."""\n+    gbs = gbs_env\n+    pipeline = \'[{"$match": {"period": "__TEST__"}}]\'\n+\n+    with patch("point_topic_mcp.core.mongodb_utils.run_aggregation", return_value=[{"period": "__TEST__"}]) as mock:\n+        result = gbs.read_gbs(pipeline_json=pipeline)\n+        parsed = json.loads(result)\n+        assert parsed[0]["period"] == "__TEST__"\n+\n+\n+def test_discovery_uses_prefix(gbs_env):\n+    """Tool auto-discovery prefixes module name as gbs_query_tools_read_gbs."""\n+    import point_topic_mcp.tools as tools\n+    all_tools = tools.get_all_tools()\n+    names = [n for n, _ in all_tools]\n+    assert "gbs_query_tools_read_gbs" in names\n\n```'}]