This is an automated email from the ASF dual-hosted git repository.

FreeOnePlus pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris-mcp-server.git


The following commit(s) were added to refs/heads/master by this push:
     new e9cf89b  fix: harden token pool timeout recovery (#159)
e9cf89b is described below

commit e9cf89ba4e514df819c64717069a8c6812789b13
Author: Yijia Su <[email protected]>
AuthorDate: Thu Jul 30 19:09:04 2026 +0800

    fix: harden token pool timeout recovery (#159)
---
 .env.example                                    |   3 +
 CHANGELOG.md                                    |   2 +
 README.md                                       |   3 +-
 doris_mcp_server/utils/config.py                |  16 +++
 doris_mcp_server/utils/db.py                    |  94 ++++++++---------
 doris_mcp_server/utils/secret_policy.py         |   1 +
 doris_mcp_server/utils/security.py              |  99 ++++++++++++++----
 test/integration/test_real_doris_transports.py  | 108 +++++++++++++++++++
 test/security/test_token_database_validation.py | 131 ++++++++++++++++++++++++
 test/utils/test_doris_user_pool_manager.py      |  58 +++++++++++
 10 files changed, 441 insertions(+), 74 deletions(-)

diff --git a/.env.example b/.env.example
index 1b9ff10..0f731a3 100644
--- a/.env.example
+++ b/.env.example
@@ -100,6 +100,9 @@ TOKEN_FILE_PATH=tokens.json
 ENABLE_TOKEN_EXPIRY=true
 DEFAULT_TOKEN_EXPIRY_HOURS=720
 TOKEN_HASH_ALGORITHM=sha256
+# Cache successful token-bound Doris route checks so MCP pings and discovery
+# requests do not reconnect on every request. Set 0 to validate every request.
+TOKEN_DB_VALIDATION_TTL_SECONDS=30
 # No static bearer token is shipped. Generate one outside source control:
 # python -c 'import secrets; print(secrets.token_urlsafe(32))'
 # TOKEN_ADMIN=<paste-generated-value-here>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1e9fa59..18b3470 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -86,6 +86,8 @@ under **Unreleased** until a new version is selected and 
published.
 
 - Preserved service availability after malformed requests, unknown methods,
   header mismatches, unsupported versions, and missing capabilities.
+- Kept static-token Doris pools usable after repeated query timeouts, and
+  stopped per-request token validation from mutating the shared global pool.
 - Stopped list operations from converting Doris, permission, or internal
   failures into successful empty resource, tool, or prompt collections.
 - Corrected resource and prompt error semantics and tool `isError` results.
diff --git a/README.md b/README.md
index 6f735a7..c3891b9 100644
--- a/README.md
+++ b/README.md
@@ -797,7 +797,7 @@ The Doris MCP Server includes a comprehensive 
enterprise-grade security framewor
 *   **🔐 Multi-Authentication System**: Complete Token, JWT, and OAuth 
authentication with independent control switches
 *   **🔗 Token-Bound Database Configuration**: Revolutionary approach allowing 
tokens to carry their own database connection parameters
 *   **🔄 Hot Reload Security**: Zero-downtime security configuration updates 
with intelligent token revalidation
-*   **⚡ Immediate Validation**: Real-time database and authentication 
validation at connection time
+*   **⚡ Route-Safe Validation**: Token-bound Doris routes are validated 
through their dedicated pools, with a short success cache so pings do not 
reconnect on every request
 *   **🛡️ Role-Based Authorization**: Advanced RBAC with four-tier security 
classification
 *   **🚫 Enhanced SQL Security**: Advanced SQL injection protection with 
improved pattern detection
 *   **🎭 Intelligent Data Masking**: Automatic sensitive data masking with 
user-based permissions
@@ -819,6 +819,7 @@ ENABLE_OAUTH_AUTH=false         # Enable OAuth 
authentication
 # Token Management (New in v0.6.0)
 TOKEN_FILE_PATH=tokens.json     # Token configuration file
 TOKEN_HOT_RELOAD=true          # Enable hot reloading
+TOKEN_DB_VALIDATION_TTL_SECONDS=30  # Cache successful Doris route checks
 
 # Legacy Configuration (Deprecated)
 # AUTH_TYPE=token               # Use individual switches instead
diff --git a/doris_mcp_server/utils/config.py b/doris_mcp_server/utils/config.py
index 6102c81..ff68dc3 100644
--- a/doris_mcp_server/utils/config.py
+++ b/doris_mcp_server/utils/config.py
@@ -463,6 +463,7 @@ class SecurityConfig:
     enable_token_expiry: bool = True  # Enable token expiration
     default_token_expiry_hours: int = 24 * 30  # Default expiry: 30 days
     token_hash_algorithm: str = "sha256"  # Token hashing algorithm: sha256, 
sha512
+    token_db_validation_ttl_seconds: int = 30
 
     # Token Management Security (New in v0.6.0)
     enable_http_token_management: bool = False  # Enable HTTP token management 
endpoints (default: disabled for security)
@@ -1288,6 +1289,12 @@ class DorisConfig:
             os.getenv("DEFAULT_TOKEN_EXPIRY_HOURS", 
str(config.security.default_token_expiry_hours))
         )
         config.security.token_hash_algorithm = 
os.getenv("TOKEN_HASH_ALGORITHM", config.security.token_hash_algorithm)
+        config.security.token_db_validation_ttl_seconds = int(
+            os.getenv(
+                "TOKEN_DB_VALIDATION_TTL_SECONDS",
+                str(config.security.token_db_validation_ttl_seconds),
+            )
+        )
 
         # Token Management Security Configuration (New in v0.6.0)
         config.security.enable_http_token_management = (
@@ -1780,6 +1787,11 @@ class DorisConfig:
         if self.security.token_expiry <= 0:
             errors.append("Token expiry time must be greater than 0")
 
+        if not 0 <= self.security.token_db_validation_ttl_seconds <= 3600:
+            errors.append(
+                "Token database validation TTL must be in the range 0-3600 
seconds"
+            )
+
         if self.security.max_query_complexity <= 0:
             errors.append("Maximum query complexity must be greater than 0")
 
@@ -2052,6 +2064,10 @@ def normalize_effective_auth_config(
         )
     except ValueError as exc:
         raise AuthConfigError(str(exc)) from exc
+    if not 0 <= config.security.token_db_validation_ttl_seconds <= 3600:
+        raise AuthConfigError(
+            "TOKEN_DB_VALIDATION_TTL_SECONDS must be in the range 0-3600"
+        )
 
     modern_auth_explicit = any(
         [
diff --git a/doris_mcp_server/utils/db.py b/doris_mcp_server/utils/db.py
index 71760c6..b596525 100644
--- a/doris_mcp_server/utils/db.py
+++ b/doris_mcp_server/utils/db.py
@@ -1263,7 +1263,11 @@ class DorisConnectionManager:
 
         except Exception as e:
             self.logger.error(
-                f"Session {session_id}: Failed to acquire connection from 
token pool: {e}"
+                "Session %s: Failed to acquire connection from token pool "
+                "(%s): %s",
+                session_id,
+                type(e).__name__,
+                str(e) or "<no message>",
             )
             raise
 
@@ -1368,7 +1372,7 @@ class DorisConnectionManager:
             self._token_pool_generations.clear()
 
     async def configure_for_token(self, token: str) -> tuple[bool, str]:
-        """Configure connection manager for token with new priority logic
+        """Validate the database route selected for a static token.
 
         Priority: Token-bound DB config > .env config > error
 
@@ -1381,61 +1385,45 @@ class DorisConnectionManager:
         Raises:
             RuntimeError: If no valid database configuration is available
         """
-        try:
-            # Priority 1: Try token-bound database config first
-            if self.token_manager:
-                db_config = 
self.token_manager.get_database_config_by_token(token)
-                if db_config:
-                    # Convert DatabaseConfig to dictionary
-                    token_db_config: DatabasePoolConfig = {
-                        "host": db_config.host,
-                        "port": db_config.port,
-                        "user": db_config.user,
-                        "password": db_config.password,
-                        "database": db_config.database,
-                        "charset": db_config.charset,
-                    }
-
-                    # Check if token-bound config is valid
-                    if not self._is_config_empty(
-                        token_db_config["host"]
-                    ) and not self._is_config_empty(token_db_config["user"]):
-                        self.logger.info(
-                            f"Using token-bound database configuration for 
host: {token_db_config['host']}"
-                        )
-                        self.active_db_config = token_db_config
-                        
self._update_db_params_from_config(self.active_db_config)
-
-                        # Create/recreate connection pool with token-bound 
config
-                        await self._ensure_pool_with_current_config()
-
-                        return True, "token-bound"
+        current_token_config = self._get_current_token_db_config(token)
+        uses_token_config = bool(
+            current_token_config
+            and not self._is_config_empty(current_token_config.get("host"))
+            and not self._is_config_empty(current_token_config.get("user"))
+        )
+        config_source = "token-bound" if uses_token_config else "global-env"
 
-            # Priority 2: Use global .env config if available
-            if self._has_valid_global_config():
-                self.logger.info("Using global .env database configuration")
-                self.active_db_config = self.original_db_config.copy()
-                self._update_db_params_from_config(self.active_db_config)
-
-                # Create/recreate connection pool with global config
-                await self._ensure_pool_with_current_config()
-
-                return True, "global-env"
-
-            # Priority 3: No valid configuration available
-            error_msg = (
-                "No valid database configuration available for this token. "
-                "Please contact administrator to:\n"
-                "1. Add database configuration to tokens.json for this token, 
OR\n"
-                "2. Configure valid global database settings in .env file\n"
-                "Required fields: DB_HOST, DB_USER"
+        connection: DorisConnection | None = None
+        try:
+            # Validate the same dedicated route that query execution will use.
+            # Never mutate active_db_config or rebuild the shared global pool
+            # while authenticating a tenant token.
+            connection = await self.get_connection_for_token(
+                token,
+                f"token_validation_{self._get_token_hash(token)[:8]}",
             )
-            self.logger.error(error_msg)
-            raise RuntimeError(error_msg)
-
+            result = await asyncio.wait_for(
+                connection.execute(
+                    "SELECT 1 AS connection_check",
+                    mask_result=False,
+                    max_rows=1,
+                    max_bytes=256,
+                ),
+                timeout=self.connect_timeout,
+            )
+            if result.data != [{"connection_check": 1}]:
+                raise RuntimeError("Token database validation query returned 
no result")
+            return True, config_source
         except Exception as e:
-            self.logger.error(f"Failed to configure database for token: {e}")
+            self.logger.error(
+                "Failed to validate database route for token (%s): %s",
+                type(e).__name__,
+                str(e) or "<no message>",
+            )
             raise
+        finally:
+            if connection is not None:
+                await self.release_connection_for_token(token, connection)
 
     async def _ensure_pool_with_current_config(self) -> None:
         """Ensure connection pool exists with current configuration"""
diff --git a/doris_mcp_server/utils/secret_policy.py 
b/doris_mcp_server/utils/secret_policy.py
index 60a3b76..8eef510 100644
--- a/doris_mcp_server/utils/secret_policy.py
+++ b/doris_mcp_server/utils/secret_policy.py
@@ -43,6 +43,7 @@ _RESERVED_TOKEN_ENV_NAMES = frozenset(
         "TOKEN_FILE_PATH",
         "TOKEN_HASH_ALGORITHM",
         "TOKEN_HOT_RELOAD",
+        "TOKEN_DB_VALIDATION_TTL_SECONDS",
         "TOKEN_SECRET",
     }
 )
diff --git a/doris_mcp_server/utils/security.py 
b/doris_mcp_server/utils/security.py
index d721028..97f64a5 100644
--- a/doris_mcp_server/utils/security.py
+++ b/doris_mcp_server/utils/security.py
@@ -22,7 +22,10 @@ Implements enterprise-level authentication, authorization, 
SQL security validati
 
 from __future__ import annotations
 
+import asyncio
+import hashlib
 import re
+import time
 from collections.abc import Callable, Mapping
 from contextvars import ContextVar
 from contextvars import Token as ContextToken
@@ -191,6 +194,8 @@ class DorisSecurityManager:
 
         # Track initialization state
         self._initialized = False
+        self._token_database_validation_cache: dict[str, float] = {}
+        self._token_database_validation_locks: dict[str, asyncio.Lock] = {}
 
     async def initialize(self) -> None:
         """Initialize security manager components"""
@@ -215,6 +220,8 @@ class DorisSecurityManager:
         """Shutdown security manager components"""
         try:
             await self.auth_provider.shutdown()
+            self._token_database_validation_cache.clear()
+            self._token_database_validation_locks.clear()
             self._initialized = False
             self.logger.info("DorisSecurityManager shutdown completed")
 
@@ -484,10 +491,12 @@ class DorisSecurityManager:
         token: str,
         token_info: TokenInfo,
     ) -> None:
-        """Validate database configuration for token immediately during 
authentication
+        """Validate a token database route with a short successful-result 
cache.
 
-        This ensures database connectivity issues are caught at authentication 
time,
-        not during query execution, providing better user experience.
+        Authentication runs for every MCP request, including pings and 
capability
+        discovery. Reconnecting to Doris on each request creates avoidable pool
+        pressure, so successful validations are cached by token and database
+        configuration fingerprint. Failures are never cached.
 
         Args:
             token: Raw authentication token
@@ -496,29 +505,79 @@ class DorisSecurityManager:
         Raises:
             ValueError: If database configuration is invalid or connection 
fails
         """
-        try:
-            if not self.connection_manager:
-                self.logger.warning(
-                    "Connection manager not available for immediate database 
validation"
-                )
-                return
+        if not self.connection_manager:
+            self.logger.warning(
+                "Connection manager not available for immediate database 
validation"
+            )
+            return
 
-            # Configure and test database connection for this token
-            success, config_source = await 
self.connection_manager.configure_for_token(
-                token
+        cache_key = self._token_database_validation_cache_key(token, 
token_info)
+        ttl_seconds = max(
+            0,
+            int(self.config.security.token_db_validation_ttl_seconds),
+        )
+        now = time.monotonic()
+        if self._token_database_validation_cache.get(cache_key, 0.0) > now:
+            self.logger.debug(
+                "Using cached database validation for token %s",
+                token_info.token_id,
             )
+            return
 
-            if success:
+        lock = self._token_database_validation_locks.setdefault(
+            cache_key,
+            asyncio.Lock(),
+        )
+        async with lock:
+            now = time.monotonic()
+            if self._token_database_validation_cache.get(cache_key, 0.0) > now:
+                return
+            try:
+                success, config_source = (
+                    await self.connection_manager.configure_for_token(token)
+                )
+                if not success:
+                    raise ValueError("Database configuration validation 
failed")
+                if ttl_seconds:
+                    self._token_database_validation_cache[cache_key] = (
+                        time.monotonic() + ttl_seconds
+                    )
                 self.logger.info(
-                    f"Database configuration validated successfully for token 
{token_info.token_id} (source: {config_source})"
+                    "Database configuration validated successfully for token 
%s "
+                    "(source: %s)",
+                    token_info.token_id,
+                    config_source,
                 )
-            else:
-                raise ValueError("Database configuration validation failed")
+            except Exception as e:
+                self._token_database_validation_cache.pop(cache_key, None)
+                error_msg = (
+                    "Database configuration validation failed for token "
+                    f"{token_info.token_id}: {str(e) or type(e).__name__}"
+                )
+                self.logger.error(error_msg)
+                raise ValueError(error_msg) from e
 
-        except Exception as e:
-            error_msg = f"Database configuration validation failed for token 
{token_info.token_id}: {str(e)}"
-            self.logger.error(error_msg)
-            raise ValueError(error_msg)
+    @staticmethod
+    def _token_database_validation_cache_key(
+        token: str,
+        token_info: TokenInfo,
+    ) -> str:
+        """Return a non-secret cache key that changes with bound DB 
credentials."""
+        database_config = token_info.database_config
+        route = (
+            getattr(database_config, "host", ""),
+            getattr(database_config, "port", ""),
+            getattr(database_config, "user", ""),
+            getattr(database_config, "password", ""),
+            getattr(database_config, "database", ""),
+            getattr(database_config, "charset", ""),
+        )
+        digest = hashlib.sha256()
+        digest.update(token.encode("utf-8"))
+        for value in route:
+            digest.update(b"\0")
+            digest.update(str(value).encode("utf-8"))
+        return digest.hexdigest()
 
 
 class AuthenticationProvider:
diff --git a/test/integration/test_real_doris_transports.py 
b/test/integration/test_real_doris_transports.py
index cc6ea74..48e1236 100644
--- a/test/integration/test_real_doris_transports.py
+++ b/test/integration/test_real_doris_transports.py
@@ -45,10 +45,12 @@ from pathlib import Path
 from typing import Any
 
 import httpx
+import httpx2
 import pymysql
 import pytest
 from mcp import Client, MCPError, StdioServerParameters
 from mcp.client.stdio import stdio_client
+from mcp.client.streamable_http import streamable_http_client
 from mcp.types import (
     SubscriptionFilter,
     SubscriptionsListenRequest,
@@ -57,6 +59,7 @@ from mcp.types import (
 )
 
 from doris_mcp_server import __version__
+from doris_mcp_server.utils.secret_policy import build_token_digest
 
 pytestmark = [
     pytest.mark.integration,
@@ -313,6 +316,28 @@ async def _http_client(
             yield client
 
 
+@asynccontextmanager
+async def _authenticated_http_client(
+    environment: dict[str, str],
+    *,
+    bearer_token: str,
+    read_timeout_seconds: int = 15,
+) -> AsyncIterator[Client]:
+    async with _http_process(environment) as base_url:
+        async with httpx2.AsyncClient(
+            headers={"Authorization": f"Bearer {bearer_token}"},
+        ) as http_client:
+            transport = streamable_http_client(
+                f"{base_url}/mcp",
+                http_client=http_client,
+            )
+            async with Client(
+                transport,
+                read_timeout_seconds=read_timeout_seconds,
+            ) as client:
+                yield client
+
+
 @asynccontextmanager
 async def _stdio_client(
     environment: dict[str, str],
@@ -537,6 +562,89 @@ async def 
test_real_doris_result_boundaries_and_cancellation(
         assert recovered_payload["data"] == [{"recovered": 1}]
 
 
+async def test_real_doris_token_pool_recovers_after_repeated_query_timeouts(
+    doris_sandbox: DorisSandbox,
+    tmp_path: Path,
+) -> None:
+    bearer_token = secrets.token_urlsafe(48)
+    token_file = tmp_path / "tokens.json"
+    token_file.write_text(
+        json.dumps(
+            {
+                "version": "2.0",
+                "tokens": [
+                    {
+                        "token_id": "real-timeout-regression",
+                        "token_digest": build_token_digest(
+                            bearer_token,
+                            "sha256",
+                        ),
+                        "created_at": "2026-07-30T00:00:00Z",
+                        "expires_at": None,
+                        "last_used": None,
+                        "description": "real token pool timeout regression",
+                        "is_active": True,
+                        "database_config": {
+                            "host": doris_sandbox.settings.host,
+                            "port": doris_sandbox.settings.port,
+                            "user": doris_sandbox.readonly_user,
+                            "password": doris_sandbox.readonly_password,
+                            "database": doris_sandbox.settings.database,
+                            "charset": "UTF8",
+                        },
+                    }
+                ],
+            }
+        ),
+        encoding="utf-8",
+    )
+    token_file.chmod(0o600)
+    environment = _server_environment(
+        doris_sandbox.settings,
+        user=doris_sandbox.settings.user,
+        password=doris_sandbox.settings.password,
+    )
+    environment.update(
+        {
+            "ENABLE_TOKEN_AUTH": "true",
+            "TOKEN_FILE_PATH": str(token_file),
+            "TOKEN_DB_VALIDATION_TTL_SECONDS": "60",
+            "DORIS_MAX_CONNECTIONS": "1",
+            "QUERY_TIMEOUT": "5",
+        }
+    )
+
+    async with _authenticated_http_client(
+        environment,
+        bearer_token=bearer_token,
+        read_timeout_seconds=10,
+    ) as client:
+        for attempt in range(3):
+            timed_out, timed_out_payload = await _exec_query(
+                client,
+                "SELECT SLEEP(3) AS slept",
+                max_rows=1,
+                max_bytes=256,
+                timeout=1,
+            )
+            assert timed_out.is_error is True
+            assert timed_out_payload["error_type"] == "timeout"
+
+            recovery_started = time.monotonic()
+            recovered, recovered_payload = await _exec_query(
+                client,
+                "SELECT CURRENT_USER() AS current_user",
+                max_rows=1,
+                max_bytes=256,
+                timeout=5,
+            )
+            assert time.monotonic() - recovery_started < 3
+            assert recovered.is_error is False, f"recovery {attempt + 1} 
failed"
+            assert doris_sandbox.readonly_user in recovered_payload["data"][0][
+                "current_user"
+            ]
+
+
 async def _collect_list_pages(
     list_method: Any,
     *,
diff --git a/test/security/test_token_database_validation.py 
b/test/security/test_token_database_validation.py
new file mode 100644
index 0000000..f719454
--- /dev/null
+++ b/test/security/test_token_database_validation.py
@@ -0,0 +1,131 @@
+#!/usr/bin/env python3
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+"""Regression tests for static-token database route validation."""
+
+from unittest.mock import AsyncMock
+
+import pytest
+
+from doris_mcp_server.auth.token_manager import DatabaseConfig, TokenInfo
+from doris_mcp_server.utils.config import (
+    AuthConfigError,
+    DorisConfig,
+    normalize_effective_auth_config,
+)
+from doris_mcp_server.utils.secret_policy import (
+    is_static_token_environment_variable,
+)
+from doris_mcp_server.utils.security import DorisSecurityManager
+
+
+def _token_info(password: str = "tenant-password") -> TokenInfo:
+    return TokenInfo(
+        token_id="tenant-alpha",
+        database_config=DatabaseConfig(
+            host="tenant-fe",
+            port=9030,
+            user="tenant-reader",
+            password=password,
+            database="tenant-db",
+            charset="UTF8",
+        ),
+    )
+
+
[email protected]
+async def test_successful_token_database_validation_is_cached(test_config):
+    connection_manager = AsyncMock()
+    connection_manager.configure_for_token.return_value = (True, "token-bound")
+    test_config.security.token_db_validation_ttl_seconds = 30
+    manager = DorisSecurityManager(test_config, connection_manager)
+    token_info = _token_info()
+
+    await manager._validate_token_database_config("high-entropy-token", 
token_info)
+    await manager._validate_token_database_config("high-entropy-token", 
token_info)
+
+    connection_manager.configure_for_token.assert_awaited_once_with(
+        "high-entropy-token"
+    )
+
+
[email protected]
+async def 
test_database_credential_rotation_invalidates_validation_cache(test_config):
+    connection_manager = AsyncMock()
+    connection_manager.configure_for_token.return_value = (True, "token-bound")
+    test_config.security.token_db_validation_ttl_seconds = 30
+    manager = DorisSecurityManager(test_config, connection_manager)
+
+    await manager._validate_token_database_config(
+        "high-entropy-token",
+        _token_info("password-v1"),
+    )
+    await manager._validate_token_database_config(
+        "high-entropy-token",
+        _token_info("password-v2"),
+    )
+
+    assert connection_manager.configure_for_token.await_count == 2
+
+
[email protected]
+async def test_failed_token_database_validation_is_not_cached(test_config):
+    connection_manager = AsyncMock()
+    connection_manager.configure_for_token.side_effect = [
+        RuntimeError("Doris unavailable"),
+        (True, "token-bound"),
+    ]
+    test_config.security.token_db_validation_ttl_seconds = 30
+    manager = DorisSecurityManager(test_config, connection_manager)
+    token_info = _token_info()
+
+    with pytest.raises(ValueError, match="Doris unavailable"):
+        await manager._validate_token_database_config(
+            "high-entropy-token",
+            token_info,
+        )
+    await manager._validate_token_database_config("high-entropy-token", 
token_info)
+
+    assert connection_manager.configure_for_token.await_count == 2
+
+
+def test_token_database_validation_ttl_loads_from_environment(
+    monkeypatch,
+    tmp_path,
+):
+    monkeypatch.setenv("TOKEN_DB_VALIDATION_TTL_SECONDS", "45")
+
+    config = DorisConfig.from_env(str(tmp_path / "missing.env"))
+
+    assert config.security.token_db_validation_ttl_seconds == 45
+
+
+def test_token_database_validation_ttl_is_not_treated_as_a_bearer_token():
+    assert (
+        is_static_token_environment_variable(
+            "TOKEN_DB_VALIDATION_TTL_SECONDS"
+        )
+        is False
+    )
+
+
+def test_token_database_validation_ttl_rejects_out_of_range_values():
+    config = DorisConfig()
+    config.security.token_db_validation_ttl_seconds = 3601
+
+    with pytest.raises(AuthConfigError, 
match="TOKEN_DB_VALIDATION_TTL_SECONDS"):
+        normalize_effective_auth_config(config)
diff --git a/test/utils/test_doris_user_pool_manager.py 
b/test/utils/test_doris_user_pool_manager.py
index 8540382..bbbde42 100644
--- a/test/utils/test_doris_user_pool_manager.py
+++ b/test/utils/test_doris_user_pool_manager.py
@@ -413,3 +413,61 @@ async def 
test_release_routed_connection_releases_closed_raw_to_captured_owner(
     assert old_owner.release_calls == [raw_connection]
     assert current_pool.release_calls == []
     assert raw_connection.ensure_closed_calls == 0
+
+
[email protected]
+async def 
test_configure_for_token_validates_dedicated_route_without_global_mutation(
+    manager,
+    monkeypatch,
+):
+    token = "tenant-token"
+    manager.token_manager = SimpleNamespace(
+        get_database_config_by_token=lambda raw: (
+            SimpleNamespace(
+                host="tenant-fe",
+                port=19030,
+                user="tenant_reader",
+                password="tenant_pw",
+                database="tenant_db",
+                charset="utf8",
+            )
+            if raw == token
+            else None
+        )
+    )
+    connection = SimpleNamespace(
+        execute=AsyncMock(
+            return_value=QueryResult(
+                data=[{"connection_check": 1}],
+                metadata={},
+                execution_time=0.0,
+                row_count=1,
+                sql="SELECT 1 AS connection_check",
+            )
+        )
+    )
+    get_connection = AsyncMock(return_value=connection)
+    release_connection = AsyncMock()
+    monkeypatch.setattr(manager, "get_connection_for_token", get_connection)
+    monkeypatch.setattr(
+        manager,
+        "release_connection_for_token",
+        release_connection,
+    )
+    original_config = manager.original_db_config.copy()
+    active_config = manager.active_db_config.copy()
+
+    success, source = await manager.configure_for_token(token)
+
+    assert success is True
+    assert source == "token-bound"
+    get_connection.assert_awaited_once()
+    connection.execute.assert_awaited_once_with(
+        "SELECT 1 AS connection_check",
+        mask_result=False,
+        max_rows=1,
+        max_bytes=256,
+    )
+    release_connection.assert_awaited_once_with(token, connection)
+    assert manager.original_db_config == original_config
+    assert manager.active_db_config == active_config


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to