Skip to content

Commit e04537d

Browse files
committed
add UT
1 parent fee5683 commit e04537d

File tree

5 files changed

+1225
-0
lines changed

5 files changed

+1225
-0
lines changed
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
# --------------------------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for license information.
4+
# --------------------------------------------------------------------------------------------
5+
6+
"""
7+
Unit tests for AKSAgentManager.
8+
"""
9+
10+
import unittest
11+
from unittest.mock import MagicMock, Mock, PropertyMock, patch
12+
13+
from azext_aks_agent.agent.k8s.aks_agent_manager import AKSAgentManager
14+
from kubernetes.client.rest import ApiException
15+
16+
17+
class TestAKSAgentManager(unittest.TestCase):
18+
"""Test cases for AKSAgentManager."""
19+
20+
def setUp(self):
21+
"""Set up test fixtures."""
22+
self.namespace = "aks-agent"
23+
self.resource_group = "test-rg"
24+
self.cluster_name = "test-cluster"
25+
self.subscription_id = "test-sub-id"
26+
self.kubeconfig_path = "/mock/kubeconfig"
27+
28+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
29+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
30+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
31+
def test_init_default_values(self, mock_helm_manager, mock_load_config, mock_init_client):
32+
"""Test AKSAgentManager initialization with default values."""
33+
manager = AKSAgentManager()
34+
35+
self.assertEqual(manager.namespace, "kube-system")
36+
self.assertEqual(manager.helm_release_name, "aks-agent")
37+
self.assertIsNotNone(manager.llm_config_manager)
38+
mock_init_client.assert_called_once()
39+
mock_load_config.assert_called_once()
40+
41+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
42+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
43+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
44+
def test_init_custom_values(self, mock_helm_manager, mock_load_config, mock_init_client):
45+
"""Test AKSAgentManager initialization with custom values."""
46+
manager = AKSAgentManager(
47+
namespace=self.namespace,
48+
kubeconfig_path=self.kubeconfig_path,
49+
resource_group_name=self.resource_group,
50+
cluster_name=self.cluster_name,
51+
subscription_id=self.subscription_id
52+
)
53+
54+
self.assertEqual(manager.namespace, self.namespace)
55+
self.assertEqual(manager.kubeconfig_path, self.kubeconfig_path)
56+
self.assertEqual(manager.resource_group_name, self.resource_group)
57+
self.assertEqual(manager.cluster_name, self.cluster_name)
58+
self.assertEqual(manager.subscription_id, self.subscription_id)
59+
60+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
61+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
62+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
63+
def test_set_aks_context(self, mock_helm_manager, mock_load_config, mock_init_client):
64+
"""Test setting AKS context."""
65+
manager = AKSAgentManager()
66+
67+
manager.set_aks_context(
68+
resource_group_name=self.resource_group,
69+
cluster_name=self.cluster_name,
70+
subscription_id=self.subscription_id
71+
)
72+
73+
self.assertEqual(manager.resource_group_name, self.resource_group)
74+
self.assertEqual(manager.cluster_name, self.cluster_name)
75+
self.assertEqual(manager.subscription_id, self.subscription_id)
76+
77+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.client.CoreV1Api')
78+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.config.load_kube_config')
79+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
80+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
81+
def test_get_agent_pods_success(self, mock_helm_manager, mock_load_config, mock_load_kube, mock_core_api):
82+
"""Test getting agent pods successfully."""
83+
# Setup mock pods with Running status
84+
mock_aks_agent_pod = Mock()
85+
mock_aks_agent_pod.metadata.name = "aks-agent-pod-1"
86+
mock_aks_agent_pod.status.phase = "Running"
87+
88+
mock_aks_mcp_pod = Mock()
89+
mock_aks_mcp_pod.metadata.name = "aks-mcp-pod-1"
90+
mock_aks_mcp_pod.status.phase = "Running"
91+
92+
# Mock pod lists for each label selector
93+
mock_agent_pod_list = Mock()
94+
mock_agent_pod_list.items = [mock_aks_agent_pod]
95+
96+
mock_mcp_pod_list = Mock()
97+
mock_mcp_pod_list.items = [mock_aks_mcp_pod]
98+
99+
mock_api_instance = Mock()
100+
# Return different pod lists for each call (agent pods, then mcp pods)
101+
mock_api_instance.list_namespaced_pod.side_effect = [mock_agent_pod_list, mock_mcp_pod_list]
102+
mock_core_api.return_value = mock_api_instance
103+
104+
manager = AKSAgentManager()
105+
success, result = manager.get_agent_pods()
106+
107+
self.assertTrue(success)
108+
self.assertIsInstance(result, list)
109+
self.assertEqual(len(result), 2)
110+
self.assertIn("aks-agent-pod-1", result)
111+
self.assertIn("aks-mcp-pod-1", result)
112+
113+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.client.CoreV1Api')
114+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.config.load_kube_config')
115+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
116+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
117+
def test_get_agent_pods_no_pods(self, mock_helm_manager, mock_load_config, mock_load_kube, mock_core_api):
118+
"""Test getting agent pods when no pods exist."""
119+
mock_pod_list = Mock()
120+
mock_pod_list.items = []
121+
122+
mock_api_instance = Mock()
123+
mock_api_instance.list_namespaced_pod.return_value = mock_pod_list
124+
mock_core_api.return_value = mock_api_instance
125+
126+
manager = AKSAgentManager()
127+
success, result = manager.get_agent_pods()
128+
129+
self.assertFalse(success)
130+
self.assertIsInstance(result, str)
131+
self.assertIn("No pods found", result)
132+
133+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
134+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
135+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
136+
def test_check_llm_config_exists_true(self, mock_helm_manager, mock_load_config, mock_init_client):
137+
"""Test checking if LLM config exists - returns True when both secret and model_list exist."""
138+
manager = AKSAgentManager()
139+
140+
# Mock the core_v1 API
141+
mock_secret = Mock()
142+
manager.core_v1 = Mock()
143+
manager.core_v1.read_namespaced_secret.return_value = mock_secret
144+
145+
# Set model_list to non-empty dict
146+
manager.llm_config_manager.model_list = {"model1": {"provider": "openai"}}
147+
148+
result = manager.check_llm_config_exists()
149+
self.assertTrue(result)
150+
151+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
152+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
153+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
154+
def test_check_llm_config_exists_false_empty_model_list(self, mock_helm_manager, mock_load_config, mock_init_client):
155+
"""Test checking if LLM config exists - returns False when secret exists but model_list is empty."""
156+
manager = AKSAgentManager()
157+
158+
# Mock the core_v1 API
159+
mock_secret = Mock()
160+
manager.core_v1 = Mock()
161+
manager.core_v1.read_namespaced_secret.return_value = mock_secret
162+
163+
# Set model_list to empty dict
164+
manager.llm_config_manager.model_list = {}
165+
166+
result = manager.check_llm_config_exists()
167+
self.assertFalse(result)
168+
169+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
170+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
171+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
172+
def test_check_llm_config_exists_false_404(self, mock_helm_manager, mock_load_config, mock_init_client):
173+
"""Test checking if LLM config exists - returns False for 404."""
174+
manager = AKSAgentManager()
175+
176+
# Mock the core_v1 API to raise ApiException with 404
177+
manager.core_v1 = Mock()
178+
manager.core_v1.read_namespaced_secret.side_effect = ApiException(status=404)
179+
180+
result = manager.check_llm_config_exists()
181+
self.assertFalse(result)
182+
183+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
184+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
185+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
186+
def test_check_llm_config_exists_raises_azcli_error(self, mock_helm_manager, mock_load_config, mock_init_client):
187+
"""Test checking if LLM config exists - raises AzCLIError for unexpected errors."""
188+
from azure.cli.core.azclierror import AzCLIError
189+
190+
manager = AKSAgentManager()
191+
192+
# Mock the core_v1 API to raise a generic exception
193+
manager.core_v1 = Mock()
194+
manager.core_v1.read_namespaced_secret.side_effect = ValueError("Unexpected error")
195+
196+
with self.assertRaises(AzCLIError) as context:
197+
manager.check_llm_config_exists()
198+
199+
self.assertIn("Failed to check LLM config existence", str(context.exception))
200+
201+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._run_helm_command')
202+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
203+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
204+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
205+
def test_deploy_agent_success(self, mock_helm_manager, mock_load_config, mock_init_client, mock_helm_cmd):
206+
"""Test successful agent deployment."""
207+
mock_helm_cmd.return_value = (True, "deployed successfully")
208+
209+
manager = AKSAgentManager()
210+
success, error_msg = manager.deploy_agent()
211+
212+
self.assertTrue(success)
213+
self.assertEqual(error_msg, "")
214+
mock_helm_cmd.assert_called()
215+
216+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._run_helm_command')
217+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
218+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
219+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
220+
def test_deploy_agent_failure(self, mock_helm_manager, mock_load_config, mock_init_client, mock_helm_cmd):
221+
"""Test agent deployment failure returns False and error message."""
222+
mock_helm_cmd.return_value = (False, "deployment failed")
223+
224+
manager = AKSAgentManager()
225+
success, error_msg = manager.deploy_agent()
226+
227+
self.assertFalse(success)
228+
self.assertIn("deployment failed", error_msg)
229+
230+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._run_helm_command')
231+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
232+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
233+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
234+
def test_uninstall_agent_success_with_secret_deletion(self, mock_helm_manager, mock_load_config,
235+
mock_init_client, mock_helm_cmd):
236+
"""Test successful agent uninstallation with secret deletion."""
237+
mock_helm_cmd.return_value = (True, "uninstalled successfully")
238+
239+
manager = AKSAgentManager()
240+
manager.delete_llm_config_secret = Mock()
241+
242+
result = manager.uninstall_agent(delete_secret=True)
243+
244+
self.assertTrue(result)
245+
manager.delete_llm_config_secret.assert_called_once()
246+
247+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._run_helm_command')
248+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
249+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
250+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
251+
def test_uninstall_agent_success_without_secret_deletion(self, mock_helm_manager, mock_load_config,
252+
mock_init_client, mock_helm_cmd):
253+
"""Test successful agent uninstallation without secret deletion."""
254+
mock_helm_cmd.return_value = (True, "uninstalled successfully")
255+
256+
manager = AKSAgentManager()
257+
manager.delete_llm_config_secret = Mock()
258+
259+
result = manager.uninstall_agent(delete_secret=False)
260+
261+
self.assertTrue(result)
262+
manager.delete_llm_config_secret.assert_not_called()
263+
264+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
265+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
266+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
267+
def test_create_llm_config_secret(self, mock_helm_manager, mock_load_config, mock_init_client):
268+
"""Test creating LLM config secret."""
269+
manager = AKSAgentManager()
270+
manager.core_v1 = Mock()
271+
manager.llm_config_manager = Mock()
272+
manager.llm_config_manager.get_llm_model_secret_data.return_value = {"API_KEY": "encoded"}
273+
manager.llm_config_manager.get_env_vars.return_value = []
274+
275+
# Mock existing secret check
276+
manager.core_v1.read_namespaced_secret.side_effect = ApiException(status=404)
277+
manager.core_v1.create_namespaced_secret.return_value = Mock()
278+
279+
manager.create_llm_config_secret()
280+
281+
manager.core_v1.create_namespaced_secret.assert_called_once()
282+
283+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
284+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
285+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
286+
def test_delete_llm_config_secret(self, mock_helm_manager, mock_load_config, mock_init_client):
287+
"""Test deleting LLM config secret."""
288+
manager = AKSAgentManager()
289+
manager.core_v1 = Mock()
290+
manager.core_v1.delete_namespaced_secret.return_value = Mock()
291+
292+
manager.delete_llm_config_secret()
293+
294+
manager.core_v1.delete_namespaced_secret.assert_called_once()
295+
296+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
297+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
298+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
299+
def test_get_default_cluster_role(self, mock_helm_manager, mock_load_config, mock_init_client):
300+
"""Test getting default cluster role."""
301+
manager = AKSAgentManager()
302+
303+
cluster_role = manager.get_default_cluster_role()
304+
305+
self.assertIsNotNone(cluster_role)
306+
self.assertEqual(cluster_role.metadata.name, "aks-agent-aks-mcp")
307+
self.assertIsNotNone(cluster_role.rules)
308+
self.assertGreater(len(cluster_role.rules), 0)
309+
310+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._run_helm_command')
311+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._init_k8s_client')
312+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.AKSAgentManager._load_existing_helm_release_config')
313+
@patch('azext_aks_agent.agent.k8s.aks_agent_manager.HelmManager')
314+
def test_get_agent_status(self, mock_helm_manager, mock_load_config, mock_init_client, mock_helm_cmd):
315+
"""Test getting agent status."""
316+
import json
317+
318+
# Mock helm commands
319+
mock_helm_cmd.side_effect = [
320+
(True, json.dumps([{"name": "aks-agent", "status": "deployed"}])),
321+
(True, json.dumps({"info": {"status": "deployed"}}))
322+
]
323+
324+
manager = AKSAgentManager()
325+
manager.core_v1 = Mock()
326+
manager.apps_v1 = Mock()
327+
328+
# Helper to create mock deployment
329+
def create_deployment(name):
330+
mock_dep = Mock()
331+
mock_dep.metadata.name = name
332+
mock_dep.status.replicas = mock_dep.status.ready_replicas = 1
333+
mock_dep.status.updated_replicas = mock_dep.status.available_replicas = 1
334+
return mock_dep
335+
336+
# Helper to create mock pod
337+
def create_pod(name):
338+
mock_pod = Mock()
339+
mock_pod.metadata.name = name
340+
mock_pod.status.phase = "Running"
341+
mock_pod.status.conditions = [Mock(type="Ready", status="True")]
342+
return mock_pod
343+
344+
# Mock deployments and pods
345+
manager.apps_v1.list_namespaced_deployment.side_effect = [
346+
Mock(items=[create_deployment("aks-agent-deployment")]),
347+
Mock(items=[create_deployment("aks-mcp-deployment")])
348+
]
349+
manager.core_v1.list_namespaced_pod.side_effect = [
350+
Mock(items=[create_pod("aks-agent-pod-1")]),
351+
Mock(items=[create_pod("aks-mcp-pod-1")])
352+
]
353+
354+
status = manager.get_agent_status()
355+
356+
self.assertIsNotNone(status)
357+
self.assertIn("helm_status", status)
358+
self.assertEqual(len(status["deployments"]), 2)
359+
self.assertEqual(len(status["pods"]), 2)
360+
361+
362+
if __name__ == '__main__':
363+
unittest.main()

0 commit comments

Comments
 (0)