[{'Text': 'Edited point-topic-mcp/tests/test_gbs_tools.py:\n\n```diff\n@@ -1,4 +1,4 @@\n-"""Tests for GBS tools (add_statistic, create_source).\n+"""Tests for GBS tools (read_gbs, add_statistic, create_source).\n \n These mock mongodb_utils to test validation, formatting, and error paths\n without requiring a real MongoDB connection.\n@@ -21,6 +21,143 @@\n         importlib.reload(gbs)\n         yield gbs\n \n+\n+# --- read_gbs tests ---\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+        call_args = mock.call_args\n+        assert call_args is not None\n+        pipe_arg = call_args[0][0]\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+# --- add_statistic tests ---\n \n def test_add_statistic_validation(gbs_env):\n     """add_statistic returns errors for invalid inputs."""\n@@ -57,6 +194,8 @@\n         assert "Duplicate" in result\n         assert existing_id in result\n \n+\n+# --- create_source tests ---\n \n def test_create_source_validation(gbs_env):\n     """create_source returns errors for invalid inputs."""\n@@ -99,11 +238,14 @@\n         assert existing_id in result\n \n \n+# --- discovery tests ---\n+\n def test_discovery_uses_prefix(gbs_env):\n-    """Tool auto-discovery prefixes module name."""\n+    """Tool auto-discovery prefixes module name as gbs_tools_read_gbs etc."""\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_tools_read_gbs" in names\n     assert "gbs_tools_add_statistic" in names\n     assert "gbs_tools_create_source" in names\n     assert "gbs_tools_get_status" not in names, "get_status was removed"\n\n```'}]