Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 88 additions & 0 deletions test/client_v2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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'))
Expand Down Expand Up @@ -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()
12 changes: 10 additions & 2 deletions v2/nacos/common/client_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = []
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions v2/nacos/transport/auth_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down