diff --git a/test/client_v2_test.py b/test/client_v2_test.py index fb4eddc..b79f4fe 100644 --- a/test/client_v2_test.py +++ b/test/client_v2_test.py @@ -2,6 +2,8 @@ import os import unittest from typing import List +from unittest.mock import AsyncMock +from unittest.mock import AsyncMock, MagicMock from v2.nacos import ConfigParam from v2.nacos.common.client_config import GRPCConfig @@ -12,6 +14,8 @@ from v2.nacos.naming.nacos_naming_service import NacosNamingService from v2.nacos.config.nacos_config_service import NacosConfigService from v2.nacos.common.auth import CredentialsProvider, Credentials +from v2.nacos.common.client_config import ClientConfig +from v2.nacos.transport.auth_client import AuthClient client_config = (ClientConfigBuilder() .access_key(os.getenv('NACOS_ACCESS_KEY')) @@ -189,6 +193,90 @@ async def cb(instance_list: List[Instance]): await client.shutdown() + async def test_auth_login_url_with_root_context_path(self): + """ + [Issue #300] Verifies that when context_path is '/', the login URL + is correctly formed as '/v1/auth/users/login' without double slashes. + """ + + # 1. Setup config with root context path + config = ClientConfig( + server_addresses="http://127.0.0.1:8848", + username="nacos", + password="nacos", + context_path="/" + ) + + # 2. Create required mock objects + mock_get_server_list = MagicMock(return_value=["http://127.0.0.1:8848"]) + mock_http_agent = MagicMock() + mock_logger = MagicMock() # Mock logger + + # 3. Initialize AuthClient with ALL required arguments + client = AuthClient( + client_config=config, + get_server_list_func=mock_get_server_list, + http_agent=mock_http_agent, + logger=mock_logger + ) + + # 4. Mock the HTTP request to capture the URL + mock_response = (b'{"accessToken":"mock-token", "tokenTtl":18000}', None) + mock_request = AsyncMock(return_value=mock_response) + mock_http_agent.request = mock_request + + # 5. Execute login logic + await client.get_access_token(force_refresh=True) + + # 6. Assert the generated URL + called_url = mock_request.call_args[0][0] + expected_url = "http://127.0.0.1:8848/v1/auth/users/login" + + self.assertEqual(called_url, expected_url, + f"URL mismatch for root context_path. Expected '{expected_url}', but got '{called_url}'") + + async def test_auth_login_url_with_standard_context_path(self): + """ + [Regression Test] Verifies that when context_path is '/nacos', + the login URL correctly includes the prefix. + """ + import logging + + # 1. Setup config with standard context path + config = ClientConfig( + server_addresses="http://127.0.0.1:8848", + username="nacos", + password="nacos", + context_path="/nacos" + ) + + # 2. Create required mock objects + mock_get_server_list = MagicMock(return_value=["http://127.0.0.1:8848"]) + mock_http_agent = MagicMock() + mock_logger = MagicMock() # Mock logger + + # 3. Initialize AuthClient with ALL required arguments + client = AuthClient( + client_config=config, + get_server_list_func=mock_get_server_list, + http_agent=mock_http_agent, + logger=mock_logger + ) + + # 4. Mock the HTTP request + mock_response = (b'{"accessToken":"mock-token", "tokenTtl":18000}', None) + mock_request = AsyncMock(return_value=mock_response) + mock_http_agent.request = mock_request + + # 5. Execute login logic + await client.get_access_token(force_refresh=True) + + # 6. Assert the generated URL + called_url = mock_request.call_args[0][0] + expected_url = "http://127.0.0.1:8848/nacos/v1/auth/users/login" + + self.assertEqual(called_url, expected_url, + f"URL mismatch for standard context_path. Expected '{expected_url}', but got '{called_url}'") if __name__ == '__main__': unittest.main() diff --git a/v2/nacos/common/client_config.py b/v2/nacos/common/client_config.py index 11c9d2d..6bad69d 100644 --- a/v2/nacos/common/client_config.py +++ b/v2/nacos/common/client_config.py @@ -47,7 +47,7 @@ def __init__(self, max_receive_message_length=Constants.GRPC_MAX_RECEIVE_MESSAGE class ClientConfig: - def __init__(self, server_addresses=None, endpoint=None, namespace_id='', context_path='', access_key=None, + def __init__(self, server_addresses=None, endpoint=None, namespace_id='', context_path=Constants.WEB_CONTEXT, access_key=None, secret_key=None, username=None, password=None, app_name='', app_key='', log_dir='', log_level=None, log_rotation_backup_count=None, app_conn_labels=None, credentials_provider=None): self.server_list = [] @@ -63,7 +63,15 @@ def __init__(self, server_addresses=None, endpoint=None, namespace_id='', contex self.endpoint_query_header = None self.namespace_id = namespace_id self.credentials_provider = credentials_provider if credentials_provider else StaticCredentialsProvider(access_key, secret_key) - self.context_path = context_path + if not context_path: + self.context_path = Constants.WEB_CONTEXT + else: + cp = context_path + if not cp.startswith("/"): + cp = "/" + cp + if cp != "/" and cp.endswith("/"): + cp = cp[:-1] + self.context_path = cp self.username = username # the username for nacos auth self.password = password # the password for nacos auth self.app_name = app_name diff --git a/v2/nacos/transport/auth_client.py b/v2/nacos/transport/auth_client.py index e732d99..b80ad6a 100644 --- a/v2/nacos/transport/auth_client.py +++ b/v2/nacos/transport/auth_client.py @@ -28,10 +28,13 @@ async def get_access_token(self, force_refresh=False): "username": self.username, "password": self.password } - + ctx_path = self.client_config.context_path server_list = self.get_server_list() for server_address in server_list: - url = server_address + "/nacos/v1/auth/users/login" + if ctx_path == "/": + url = server_address + "/v1/auth/users/login" + else: + url = server_address + ctx_path + "/v1/auth/users/login" resp, error = await self.http_agent.request(url, "POST", None, params, None) if not resp or error: self.logger.warning(f"[get-access-token] request {url} failed, error: {error}")