|
16 | 16 | import importlib |
17 | 17 | import json |
18 | 18 | import os |
19 | | -import cloudpickle |
20 | | -import sys |
21 | 19 | import re |
22 | | -from unittest import mock |
| 20 | +import sys |
23 | 21 | from typing import Optional |
| 22 | +from unittest import mock |
| 23 | +import uuid |
24 | 24 |
|
| 25 | +import cloudpickle |
25 | 26 | from google import auth |
| 27 | +from google.api_core import operation as ga_operation |
26 | 28 | from google.auth import credentials as auth_credentials |
| 29 | +from google.auth.transport import mtls |
27 | 30 | from google.cloud import storage |
28 | | -import vertexai |
29 | 31 | from google.cloud import aiplatform |
30 | | -from google.cloud.aiplatform_v1 import types as aip_types |
31 | | -from google.cloud.aiplatform_v1.services import reasoning_engine_service |
| 32 | +import vertexai |
32 | 33 | from google.cloud.aiplatform import base |
33 | 34 | from google.cloud.aiplatform import initializer |
34 | | -from vertexai.agent_engines import _utils |
| 35 | +from google.cloud.aiplatform_v1 import types as aip_types |
| 36 | +from google.cloud.aiplatform_v1.services import reasoning_engine_service |
35 | 37 | from vertexai import agent_engines |
36 | | -from vertexai.agent_engines.templates import adk as adk_template |
37 | 38 | from vertexai.agent_engines import _agent_engines |
38 | | -from google.api_core import operation as ga_operation |
| 39 | +from vertexai.agent_engines import _utils |
| 40 | +from vertexai.agent_engines.templates import adk as adk_template |
39 | 41 | from google.genai import types |
40 | 42 | import pytest |
41 | | -import uuid |
42 | 43 |
|
43 | 44 |
|
44 | 45 | try: |
@@ -1066,6 +1067,7 @@ def update_agent_engine_mock(): |
1066 | 1067 |
|
1067 | 1068 | @pytest.mark.usefixtures("google_auth_mock") |
1068 | 1069 | class TestAgentEngines: |
| 1070 | + |
1069 | 1071 | def setup_method(self): |
1070 | 1072 | importlib.reload(initializer) |
1071 | 1073 | importlib.reload(aiplatform) |
@@ -1167,3 +1169,260 @@ def test_update_default_telemetry_enablement( |
1167 | 1169 | assert _utils.to_dict(deployment_spec)["env"] == [ |
1168 | 1170 | {"name": key, "value": value} for key, value in expected_env_vars.items() |
1169 | 1171 | ] |
| 1172 | + |
| 1173 | + |
| 1174 | +class TestAdkAppMtls: |
| 1175 | + """Test cases for mTLS functionality in AdkApp.""" |
| 1176 | + |
| 1177 | + def test_use_client_cert_effective_with_should_use_client_cert(self): |
| 1178 | + """Verifies that it respects the google-auth mTLS enablement check.""" |
| 1179 | + with mock.patch.object( |
| 1180 | + mtls, |
| 1181 | + "should_use_client_cert", |
| 1182 | + return_value=True, |
| 1183 | + create=True, |
| 1184 | + ): |
| 1185 | + assert adk_template._use_client_cert_effective() is True |
| 1186 | + |
| 1187 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}) |
| 1188 | + def test_use_client_cert_effective_with_env_var_true(self): |
| 1189 | + """Verifies that it falls back to the environment variable if google-auth check fails.""" |
| 1190 | + with mock.patch.object( |
| 1191 | + mtls, |
| 1192 | + "should_use_client_cert", |
| 1193 | + side_effect=AttributeError, |
| 1194 | + create=True, |
| 1195 | + ): |
| 1196 | + assert adk_template._use_client_cert_effective() is True |
| 1197 | + |
| 1198 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}) |
| 1199 | + def test_use_client_cert_effective_with_env_var_false(self): |
| 1200 | + """Verifies that it respects the environment variable being set to false.""" |
| 1201 | + with mock.patch.object( |
| 1202 | + mtls, |
| 1203 | + "should_use_client_cert", |
| 1204 | + side_effect=AttributeError, |
| 1205 | + create=True, |
| 1206 | + ): |
| 1207 | + assert adk_template._use_client_cert_effective() is False |
| 1208 | + |
| 1209 | + def test_get_api_endpoint_default(self): |
| 1210 | + """Verifies the default telemetry endpoint is returned when no mTLS is configured.""" |
| 1211 | + assert ( |
| 1212 | + adk_template._get_api_endpoint() == adk_template._DEFAULT_TELEMETRY_ENDPOINT |
| 1213 | + ) |
| 1214 | + |
| 1215 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}) |
| 1216 | + def test_get_api_endpoint_always_with_cert(self): |
| 1217 | + """Verifies the mTLS endpoint is used when forced and a certificate is available.""" |
| 1218 | + assert ( |
| 1219 | + adk_template._get_api_endpoint(client_cert_source=b"cert") |
| 1220 | + == adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT |
| 1221 | + ) |
| 1222 | + |
| 1223 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "auto"}) |
| 1224 | + def test_get_api_endpoint_auto_no_cert(self): |
| 1225 | + """Verifies it falls back to regular endpoint even if forced if no certificate is provided.""" |
| 1226 | + assert ( |
| 1227 | + adk_template._get_api_endpoint() == adk_template._DEFAULT_TELEMETRY_ENDPOINT |
| 1228 | + ) |
| 1229 | + |
| 1230 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}) |
| 1231 | + def test_get_api_endpoint_never(self): |
| 1232 | + """Verifies the regular endpoint is used when mTLS is explicitly disabled.""" |
| 1233 | + assert ( |
| 1234 | + adk_template._get_api_endpoint(client_cert_source=b"cert") |
| 1235 | + == adk_template._DEFAULT_TELEMETRY_ENDPOINT |
| 1236 | + ) |
| 1237 | + |
| 1238 | + @mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT)) |
| 1239 | + @mock.patch.object(adk_template.requests_auth, "AuthorizedSession") |
| 1240 | + @mock.patch( |
| 1241 | + "opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter" |
| 1242 | + ) |
| 1243 | + def test_default_instrumentor_builder_with_mtls( |
| 1244 | + self, |
| 1245 | + mock_exporter, |
| 1246 | + mock_session_cls, |
| 1247 | + mock_auth_default, |
| 1248 | + ): |
| 1249 | + """Integration test for the instrumentor builder with mTLS enabled.""" |
| 1250 | + # Mocking to enable mTLS |
| 1251 | + with mock.patch.object( |
| 1252 | + adk_template, "_use_client_cert_effective", return_value=True |
| 1253 | + ): |
| 1254 | + with mock.patch.object( |
| 1255 | + mtls, "has_default_client_cert_source", return_value=True |
| 1256 | + ): |
| 1257 | + with mock.patch.object( |
| 1258 | + mtls, |
| 1259 | + "default_client_cert_source", |
| 1260 | + return_value=lambda: b"cert", |
| 1261 | + ): |
| 1262 | + adk_template._default_instrumentor_builder( |
| 1263 | + _TEST_PROJECT_ID, enable_tracing=True |
| 1264 | + ) |
| 1265 | + |
| 1266 | + # Verify the session was configured for mTLS |
| 1267 | + mock_session_cls.return_value.configure_mtls_channel.assert_called_once() |
| 1268 | + # Verify the exporter was initialized with the mTLS endpoint |
| 1269 | + mock_exporter.assert_called_once() |
| 1270 | + assert ( |
| 1271 | + mock_exporter.call_args.kwargs["endpoint"] |
| 1272 | + == adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT |
| 1273 | + ) |
| 1274 | + |
| 1275 | + @mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT)) |
| 1276 | + @mock.patch.object(adk_template.requests_auth, "AuthorizedSession") |
| 1277 | + def test_warn_if_telemetry_api_disabled_with_mtls( |
| 1278 | + self, |
| 1279 | + mock_session_cls, |
| 1280 | + mock_auth_default, |
| 1281 | + ): |
| 1282 | + """Integration test for the telemetry API check with mTLS enabled.""" |
| 1283 | + mock_session = mock_session_cls.return_value |
| 1284 | + mock_session.post.return_value = mock.Mock(text="") |
| 1285 | + |
| 1286 | + # Mocking to enable mTLS |
| 1287 | + with mock.patch.object( |
| 1288 | + adk_template, "_use_client_cert_effective", return_value=True |
| 1289 | + ): |
| 1290 | + with mock.patch.object( |
| 1291 | + mtls, "has_default_client_cert_source", return_value=True |
| 1292 | + ): |
| 1293 | + with mock.patch.object( |
| 1294 | + mtls, |
| 1295 | + "default_client_cert_source", |
| 1296 | + return_value=lambda: b"cert", |
| 1297 | + ): |
| 1298 | + adk_template._warn_if_telemetry_api_disabled() |
| 1299 | + |
| 1300 | + # Verify mTLS channel was configured for the check request |
| 1301 | + mock_session.configure_mtls_channel.assert_called_once() |
| 1302 | + # Verify the check was performed against the mTLS endpoint |
| 1303 | + mock_session.post.assert_called_once_with( |
| 1304 | + adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT, data=None |
| 1305 | + ) |
| 1306 | + |
| 1307 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "invalid_value"}) |
| 1308 | + def test_get_api_endpoint_invalid_env(self): |
| 1309 | + """Verifies it defaults to AUTO and warns on invalid env var.""" |
| 1310 | + with mock.patch.object(adk_template, "_warn") as mock_warn: |
| 1311 | + assert ( |
| 1312 | + adk_template._get_api_endpoint() |
| 1313 | + == adk_template._DEFAULT_TELEMETRY_ENDPOINT |
| 1314 | + ) |
| 1315 | + mock_warn.assert_called_once() |
| 1316 | + |
| 1317 | + @mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "not_a_bool"}) |
| 1318 | + def test_use_client_cert_effective_invalid_env(self): |
| 1319 | + """Verifies it warns on invalid boolean env var.""" |
| 1320 | + with mock.patch.object( |
| 1321 | + mtls, |
| 1322 | + "should_use_client_cert", |
| 1323 | + side_effect=AttributeError, |
| 1324 | + create=True, |
| 1325 | + ): |
| 1326 | + with mock.patch.object(adk_template, "_warn") as mock_warn: |
| 1327 | + assert adk_template._use_client_cert_effective() is False |
| 1328 | + mock_warn.assert_called_once() |
| 1329 | + |
| 1330 | + def test_use_client_cert_effective_with_should_use_client_cert_false(self): |
| 1331 | + """Verifies that it respects google-auth returning False for mTLS.""" |
| 1332 | + with mock.patch.object( |
| 1333 | + mtls, |
| 1334 | + "should_use_client_cert", |
| 1335 | + return_value=False, |
| 1336 | + create=True, |
| 1337 | + ): |
| 1338 | + assert adk_template._use_client_cert_effective() is False |
| 1339 | + |
| 1340 | + def test_get_api_endpoint_auto_with_cert(self): |
| 1341 | + """Verifies the mTLS endpoint is used in AUTO mode when a cert is available.""" |
| 1342 | + # AUTO is the default, so we just pass a cert |
| 1343 | + assert ( |
| 1344 | + adk_template._get_api_endpoint(client_cert_source=b"cert") |
| 1345 | + == adk_template._DEFAULT_MTLS_TELEMETRY_ENDPOINT |
| 1346 | + ) |
| 1347 | + |
| 1348 | + @mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT)) |
| 1349 | + @mock.patch.object(adk_template.requests_auth, "AuthorizedSession") |
| 1350 | + @mock.patch( |
| 1351 | + "opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter" |
| 1352 | + ) |
| 1353 | + def test_default_instrumentor_builder_no_mtls( |
| 1354 | + self, |
| 1355 | + mock_exporter, |
| 1356 | + mock_session_cls, |
| 1357 | + mock_auth_default, |
| 1358 | + ): |
| 1359 | + """Integration test for the instrumentor builder with mTLS disabled.""" |
| 1360 | + with mock.patch.object( |
| 1361 | + adk_template, "_use_client_cert_effective", return_value=False |
| 1362 | + ): |
| 1363 | + adk_template._default_instrumentor_builder( |
| 1364 | + _TEST_PROJECT_ID, enable_tracing=True |
| 1365 | + ) |
| 1366 | + |
| 1367 | + # Verify mTLS channel was NOT configured |
| 1368 | + mock_session_cls.return_value.configure_mtls_channel.assert_not_called() |
| 1369 | + # Verify the exporter was initialized with the regular endpoint |
| 1370 | + mock_exporter.assert_called_once() |
| 1371 | + assert ( |
| 1372 | + mock_exporter.call_args.kwargs["endpoint"] |
| 1373 | + == adk_template._DEFAULT_TELEMETRY_ENDPOINT |
| 1374 | + ) |
| 1375 | + |
| 1376 | + @mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT)) |
| 1377 | + @mock.patch.object(adk_template.requests_auth, "AuthorizedSession") |
| 1378 | + def test_warn_if_telemetry_api_disabled_no_mtls( |
| 1379 | + self, |
| 1380 | + mock_session_cls, |
| 1381 | + mock_auth_default, |
| 1382 | + ): |
| 1383 | + """Integration test for the telemetry API check with mTLS disabled.""" |
| 1384 | + mock_session = mock_session_cls.return_value |
| 1385 | + mock_session.post.return_value = mock.Mock(text="") |
| 1386 | + |
| 1387 | + with mock.patch.object( |
| 1388 | + adk_template, "_use_client_cert_effective", return_value=False |
| 1389 | + ): |
| 1390 | + adk_template._warn_if_telemetry_api_disabled() |
| 1391 | + |
| 1392 | + # Verify mTLS channel was NOT configured |
| 1393 | + mock_session.configure_mtls_channel.assert_not_called() |
| 1394 | + # Verify the check was performed against the regular endpoint |
| 1395 | + mock_session.post.assert_called_once_with( |
| 1396 | + adk_template._DEFAULT_TELEMETRY_ENDPOINT, data=None |
| 1397 | + ) |
| 1398 | + |
| 1399 | + @mock.patch("google.auth.default", return_value=(mock.Mock(), _TEST_PROJECT)) |
| 1400 | + @mock.patch.object(adk_template.requests_auth, "AuthorizedSession") |
| 1401 | + @mock.patch( |
| 1402 | + "opentelemetry.exporter.otlp.proto.http.trace_exporter.OTLPSpanExporter" |
| 1403 | + ) |
| 1404 | + def test_default_instrumentor_builder_mtls_no_cert_source( |
| 1405 | + self, |
| 1406 | + mock_exporter, |
| 1407 | + mock_session_cls, |
| 1408 | + mock_auth_default, |
| 1409 | + ): |
| 1410 | + """Tests that it falls back to regular endpoint if mTLS is on but no cert is found.""" |
| 1411 | + with mock.patch.object( |
| 1412 | + adk_template, "_use_client_cert_effective", return_value=True |
| 1413 | + ): |
| 1414 | + with mock.patch.object( |
| 1415 | + mtls, |
| 1416 | + "has_default_client_cert_source", |
| 1417 | + return_value=False, |
| 1418 | + ): |
| 1419 | + adk_template._default_instrumentor_builder( |
| 1420 | + _TEST_PROJECT_ID, enable_tracing=True |
| 1421 | + ) |
| 1422 | + |
| 1423 | + # Channel is configured, but endpoint remains default due to missing cert source |
| 1424 | + mock_session_cls.return_value.configure_mtls_channel.assert_called_once() |
| 1425 | + assert ( |
| 1426 | + mock_exporter.call_args.kwargs["endpoint"] |
| 1427 | + == adk_template._DEFAULT_TELEMETRY_ENDPOINT |
| 1428 | + ) |
0 commit comments