| from __future__ import annotations |
| |
| import asyncio |
| import json |
| import logging |
| import sys |
| import tempfile |
| import unittest |
| from pathlib import Path |
| from typing import Any |
| from unittest.mock import Mock, patch |
| |
| ROOT = Path(__file__).resolve().parents[1] |
| sys.path.insert(0, str(ROOT / "src")) |
| |
| from iotdb_mcp_server.config import Config # noqa: E402 |
| from iotdb_mcp_server.session_manager import IoTDBSessionManager # noqa: E402 |
| from iotdb_mcp_server.services.database import register_database_tools # noqa: E402 |
| from iotdb_mcp_server.target_registry import ( # noqa: E402 |
| IoTDBTargetRegistry, |
| target_from_mapping, |
| ) |
| |
| |
| class _FakeMcp: |
| def __init__(self) -> None: |
| self.tools: dict[str, Any] = {} |
| |
| def tool(self): |
| def register(function): |
| self.tools[function.__name__] = function |
| return function |
| |
| return register |
| |
| |
| class DatabaseTargetRoutingTest(unittest.TestCase): |
| def test_use_database_updates_target_and_rebuilds_its_pool(self) -> None: |
| target = target_from_mapping( |
| { |
| "target_id": "table-cloud", |
| "host": "192.168.99.20", |
| "port": 6667, |
| "user": "root", |
| "password": "known-good", |
| "database": "original_db", |
| "sql_dialect": "table", |
| "verified_at": "2026-07-13T00:00:00+00:00", |
| } |
| ) |
| registry = IoTDBTargetRegistry( |
| {target.target_id: target}, |
| default_target_id=target.target_id, |
| ) |
| manager = IoTDBSessionManager(registry) |
| config = Config.from_target( |
| target, |
| target_registry=registry, |
| session_manager=manager, |
| ) |
| pool = Mock() |
| session = Mock() |
| pool.get_session.return_value = session |
| mcp = _FakeMcp() |
| register_database_tools(mcp, config, logging.getLogger(__name__)) |
| |
| with patch( |
| "iotdb_mcp_server.session_manager.create_table_session_pool", |
| return_value=pool, |
| ): |
| response = asyncio.run(mcp.tools["use_database"]("analytics")) |
| |
| session.execute_non_query_statement.assert_called_once_with("USE analytics") |
| session.close.assert_called_once() |
| pool.close.assert_called_once() |
| self.assertEqual(config.database, "analytics") |
| updated_registry = config.target_registry |
| assert updated_registry is not None |
| self.assertEqual( |
| updated_registry.resolve(target.target_id).database, |
| "analytics", |
| ) |
| self.assertEqual( |
| manager.registry.resolve(target.target_id).database, |
| "analytics", |
| ) |
| payload = json.loads(response[-1].text) |
| self.assertEqual(payload["context"]["iotdb"]["database"], "analytics") |
| |
| def test_create_database_keeps_explicit_target_through_pool_selection(self) -> None: |
| default_target = target_from_mapping( |
| { |
| "target_id": "default-cloud", |
| "host": "192.168.99.20", |
| "port": 6667, |
| "user": "root", |
| "password": "default-secret", |
| "sql_dialect": "tree", |
| "verified_at": "2026-07-13T00:00:00+00:00", |
| } |
| ) |
| explicit_target = target_from_mapping( |
| { |
| "target_id": "explicit-cloud", |
| "host": "192.168.99.15", |
| "port": 6667, |
| "user": "operator", |
| "password": "explicit-secret", |
| "sql_dialect": "tree", |
| "verified_at": "2026-07-13T00:00:00+00:00", |
| } |
| ) |
| registry = IoTDBTargetRegistry( |
| { |
| default_target.target_id: default_target, |
| explicit_target.target_id: explicit_target, |
| }, |
| default_target_id=default_target.target_id, |
| ) |
| manager = IoTDBSessionManager(registry) |
| manager.update_registry = Mock(wraps=manager.update_registry) |
| pool = Mock() |
| session = Mock() |
| pool.get_session.return_value = session |
| |
| with tempfile.TemporaryDirectory() as directory: |
| targets_file = Path(directory) / "iotdb-targets.json" |
| targets_file.write_text( |
| json.dumps(registry.as_dict(include_secret=True)), |
| encoding="utf-8", |
| ) |
| config = Config.from_target( |
| default_target, |
| target_registry=registry, |
| session_manager=manager, |
| ) |
| config.targets_file = str(targets_file) |
| config.targets_file_mtime_ns = targets_file.stat().st_mtime_ns |
| mcp = _FakeMcp() |
| register_database_tools(mcp, config, logging.getLogger(__name__)) |
| |
| selectors = ( |
| {"target_id": explicit_target.target_id}, |
| {"target": {"target_id": explicit_target.target_id}}, |
| ) |
| with patch( |
| "iotdb_mcp_server.session_manager.create_tree_session_pool", |
| return_value=pool, |
| ) as create_pool: |
| for selector in selectors: |
| with self.subTest(selector=selector): |
| create_pool.reset_mock() |
| session.execute_non_query_statement.reset_mock() |
| asyncio.run( |
| mcp.tools["create_database"]( |
| "root.explicit_test", |
| **selector, |
| ) |
| ) |
| routed_target = create_pool.call_args.args[0] |
| self.assertEqual( |
| routed_target.target_id, explicit_target.target_id |
| ) |
| self.assertEqual(routed_target.host, "192.168.99.15") |
| self.assertEqual(routed_target.user, "operator") |
| session.execute_non_query_statement.assert_called_once_with( |
| "CREATE DATABASE root.explicit_test" |
| ) |
| manager.close() |
| |
| manager.update_registry.assert_not_called() |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |