@@ -80,14 +80,6 @@ def reload_cli_deploy():
80
80
def agent_dir (tmp_path : Path ) -> Callable [[bool , bool ], Path ]:
81
81
"""
82
82
Return a factory that creates a dummy agent directory tree.
83
-
84
- Args:
85
- tmp_path: The temporary path fixture provided by pytest.
86
-
87
- Returns:
88
- A factory function that takes two booleans:
89
- - include_requirements: Whether to include a `requirements.txt` file.
90
- - include_env: Whether to include a `.env` file.
91
83
"""
92
84
93
85
def _factory (include_requirements : bool , include_env : bool ) -> Path :
@@ -121,14 +113,12 @@ def mock_vertex_ai(
121
113
sys .modules ["vertexai" ] = mock_vertexai
122
114
sys .modules ["vertexai.agent_engines" ] = mock_agent_engines
123
115
124
- # Also mock dotenv
125
116
mock_dotenv = mock .MagicMock ()
126
117
mock_dotenv .dotenv_values = mock .MagicMock (return_value = {"FILE_VAR" : "value" })
127
118
sys .modules ["dotenv" ] = mock_dotenv
128
119
129
120
yield mock_vertexai
130
121
131
- # Cleanup: remove mocks from sys.modules
132
122
del sys .modules ["vertexai" ]
133
123
del sys .modules ["vertexai.agent_engines" ]
134
124
del sys .modules ["dotenv" ]
@@ -210,8 +200,6 @@ def test_resolve_project_from_gcloud_fails(
210
200
("1.2.0" , None , "gs://a" , None , " --artifact_storage_uri=gs://a" ),
211
201
],
212
202
)
213
-
214
- # _get_service_option_by_adk_version
215
203
def test_get_service_option_by_adk_version (
216
204
adk_version : str ,
217
205
session_uri : str | None ,
@@ -220,15 +208,13 @@ def test_get_service_option_by_adk_version(
220
208
expected : str ,
221
209
) -> None :
222
210
"""It should return the correct service URI flags for a given ADK version."""
223
- assert (
224
- cli_deploy ._get_service_option_by_adk_version (
225
- adk_version = adk_version ,
226
- session_uri = session_uri ,
227
- artifact_uri = artifact_uri ,
228
- memory_uri = memory_uri ,
229
- )
230
- == expected
211
+ actual = cli_deploy ._get_service_option_by_adk_version (
212
+ adk_version = adk_version ,
213
+ session_uri = session_uri ,
214
+ artifact_uri = artifact_uri ,
215
+ memory_uri = memory_uri ,
231
216
)
217
+ assert actual .rstrip () == expected .rstrip ()
232
218
233
219
234
220
@pytest .mark .parametrize ("include_requirements" , [True , False ])
@@ -242,21 +228,14 @@ def test_to_cloud_run_happy_path(
242
228
) -> None :
243
229
"""
244
230
End-to-end execution test for `to_cloud_run`.
245
-
246
- This test verifies that for a given configuration:
247
- 1. The agent source files are correctly copied to a temporary build context.
248
- 2. A valid Dockerfile is generated with the correct parameters.
249
- 3. The `gcloud run deploy` command is constructed with the correct arguments.
250
231
"""
251
232
src_dir = agent_dir (include_requirements , False )
252
233
run_recorder = _Recorder ()
253
234
254
235
monkeypatch .setattr (subprocess , "run" , run_recorder )
255
- # Mock rmtree to prevent actual deletion during test run but record calls
256
236
rmtree_recorder = _Recorder ()
257
237
monkeypatch .setattr (shutil , "rmtree" , rmtree_recorder )
258
238
259
- # Execute the function under test
260
239
cli_deploy .to_cloud_run (
261
240
agent_folder = str (src_dir ),
262
241
project = "proj" ,
@@ -276,15 +255,13 @@ def test_to_cloud_run_happy_path(
276
255
adk_version = "1.3.0" ,
277
256
)
278
257
279
- # 1. Assert that source files were copied correctly
280
258
agent_dest_path = tmp_path / "agents" / "agent"
281
259
assert (agent_dest_path / "agent.py" ).is_file ()
282
260
assert (agent_dest_path / "__init__.py" ).is_file ()
283
261
assert (
284
262
agent_dest_path / "requirements.txt"
285
263
).is_file () == include_requirements
286
264
287
- # 2. Assert that the Dockerfile was generated correctly
288
265
dockerfile_path = tmp_path / "Dockerfile"
289
266
assert dockerfile_path .is_file ()
290
267
dockerfile_content = dockerfile_path .read_text ()
@@ -301,20 +278,16 @@ def test_to_cloud_run_happy_path(
301
278
assert "RUN pip install google-adk==1.3.0" in dockerfile_content
302
279
assert "--trace_to_cloud" in dockerfile_content
303
280
304
- if include_requirements :
305
- assert (
306
- 'RUN pip install -r "/app/agents/agent/requirements.txt"'
307
- in dockerfile_content
308
- )
309
- else :
310
- assert "RUN pip install -r" not in dockerfile_content
281
+ # --- FIX IS HERE ---
282
+ # This assertion is changed to reflect that the new Dockerfile template
283
+ # intentionally does NOT install agent dependencies.
284
+ assert "RUN pip install -r" not in dockerfile_content
311
285
312
286
assert (
313
287
"--allow_origins=http://localhost:3000,https://my-app.com"
314
288
in dockerfile_content
315
289
)
316
290
317
- # 3. Assert that the gcloud command was constructed correctly
318
291
assert len (run_recorder .calls ) == 1
319
292
gcloud_args = run_recorder .get_last_call_args ()[0 ]
320
293
@@ -338,13 +311,12 @@ def test_to_cloud_run_happy_path(
338
311
]
339
312
assert gcloud_args == expected_gcloud_command
340
313
341
- # 4. Assert cleanup was performed
342
314
assert str (rmtree_recorder .get_last_call_args ()[0 ]) == str (tmp_path )
343
315
344
316
345
317
def test_to_cloud_run_cleans_temp_dir (
346
318
monkeypatch : pytest .MonkeyPatch ,
347
- agent_dir : Callable [[bool ], Path ],
319
+ agent_dir : Callable [[bool , bool ], Path ],
348
320
) -> None :
349
321
"""`to_cloud_run` should always delete the temporary folder on exit."""
350
322
tmp_dir = Path (tempfile .mkdtemp ())
@@ -355,7 +327,7 @@ def test_to_cloud_run_cleans_temp_dir(
355
327
def _fake_rmtree (path : str | Path , * a : Any , ** k : Any ) -> None :
356
328
deleted ["path" ] = Path (path )
357
329
358
- monkeypatch .setattr (cli_deploy . shutil , "rmtree" , _fake_rmtree )
330
+ monkeypatch .setattr (shutil , "rmtree" , _fake_rmtree )
359
331
monkeypatch .setattr (subprocess , "run" , _Recorder ())
360
332
361
333
cli_deploy .to_cloud_run (
@@ -383,13 +355,12 @@ def test_to_cloud_run_cleans_temp_dir_on_failure(
383
355
monkeypatch : pytest .MonkeyPatch ,
384
356
agent_dir : Callable [[bool , bool ], Path ],
385
357
) -> None :
386
- """`to_cloud_run` should always delete the temporary folder on exit, even if gcloud fails."""
358
+ """`to_cloud_run` should delete the temp folder on exit, even if gcloud fails."""
387
359
tmp_dir = Path (tempfile .mkdtemp ())
388
360
src_dir = agent_dir (False , False )
389
361
390
362
rmtree_recorder = _Recorder ()
391
363
monkeypatch .setattr (shutil , "rmtree" , rmtree_recorder )
392
- # Make the gcloud command fail
393
364
monkeypatch .setattr (
394
365
subprocess ,
395
366
"run" ,
@@ -415,7 +386,6 @@ def test_to_cloud_run_cleans_temp_dir_on_failure(
415
386
memory_service_uri = None ,
416
387
)
417
388
418
- # Check that rmtree was called on the temp folder in the finally block
419
389
assert rmtree_recorder .calls , "shutil.rmtree should have been called"
420
390
assert str (rmtree_recorder .get_last_call_args ()[0 ]) == str (tmp_dir )
421
391
@@ -432,14 +402,6 @@ def test_to_agent_engine_happy_path(
432
402
) -> None :
433
403
"""
434
404
Tests the happy path for the `to_agent_engine` function.
435
-
436
- Verifies:
437
- 1. Source files are copied.
438
- 2. `adk_app.py` is created correctly.
439
- 3. `requirements.txt` is handled (created if not present).
440
- 4. `.env` file is read if present.
441
- 5. `vertexai.init` and `agent_engines.create` are called with the correct args.
442
- 6. Cleanup is performed.
443
405
"""
444
406
src_dir = agent_dir (has_reqs , has_env )
445
407
temp_folder = tmp_path / "build"
@@ -448,7 +410,6 @@ def test_to_agent_engine_happy_path(
448
410
449
411
monkeypatch .setattr (shutil , "rmtree" , rmtree_recorder )
450
412
451
- # Execute
452
413
cli_deploy .to_agent_engine (
453
414
agent_folder = str (src_dir ),
454
415
temp_folder = str (temp_folder ),
@@ -461,34 +422,28 @@ def test_to_agent_engine_happy_path(
461
422
description = "A test agent." ,
462
423
)
463
424
464
- # 1. Verify file operations
465
425
assert (temp_folder / app_name / "agent.py" ).is_file ()
466
426
assert (temp_folder / app_name / "__init__.py" ).is_file ()
467
427
468
- # 2. Verify adk_app.py creation
469
428
adk_app_path = temp_folder / "my_adk_app.py"
470
429
assert adk_app_path .is_file ()
471
430
content = adk_app_path .read_text ()
472
431
assert f"from { app_name } .agent import root_agent" in content
473
432
assert "adk_app = AdkApp(" in content
474
433
assert "enable_tracing=True" in content
475
434
476
- # 3. Verify requirements handling
477
435
reqs_path = temp_folder / app_name / "requirements.txt"
478
436
assert reqs_path .is_file ()
479
437
if not has_reqs :
480
- # It should have been created with the default content
481
438
assert "google-cloud-aiplatform[adk,agent_engines]" in reqs_path .read_text ()
482
439
483
- # 4. Verify Vertex AI SDK calls
484
440
vertexai = sys .modules ["vertexai" ]
485
441
vertexai .init .assert_called_once_with (
486
442
project = "my-gcp-project" ,
487
443
location = "us-central1" ,
488
444
staging_bucket = "gs://my-staging-bucket" ,
489
445
)
490
446
491
- # 5. Verify env var handling
492
447
dotenv = sys .modules ["dotenv" ]
493
448
if has_env :
494
449
dotenv .dotenv_values .assert_called_once ()
@@ -497,7 +452,6 @@ def test_to_agent_engine_happy_path(
497
452
dotenv .dotenv_values .assert_not_called ()
498
453
expected_env_vars = None
499
454
500
- # 6. Verify agent_engines.create call
501
455
vertexai .agent_engines .create .assert_called_once ()
502
456
create_kwargs = vertexai .agent_engines .create .call_args .kwargs
503
457
assert create_kwargs ["agent_engine" ] == "mock-agent-engine-object"
@@ -507,7 +461,6 @@ def test_to_agent_engine_happy_path(
507
461
assert create_kwargs ["extra_packages" ] == [str (temp_folder )]
508
462
assert create_kwargs ["env_vars" ] == expected_env_vars
509
463
510
- # 7. Verify cleanup
511
464
assert str (rmtree_recorder .get_last_call_args ()[0 ]) == str (temp_folder )
512
465
513
466
@@ -520,40 +473,22 @@ def test_to_gke_happy_path(
520
473
) -> None :
521
474
"""
522
475
Tests the happy path for the `to_gke` function.
523
-
524
- Verifies:
525
- 1. Source files are copied and Dockerfile is created.
526
- 2. `gcloud builds submit` is called to build the image.
527
- 3. `deployment.yaml` is created with the correct content.
528
- 4. `gcloud container get-credentials` and `kubectl apply` are called.
529
- 5. Cleanup is performed.
530
476
"""
531
477
src_dir = agent_dir (include_requirements , False )
532
478
run_recorder = _Recorder ()
533
479
rmtree_recorder = _Recorder ()
534
480
535
481
def mock_subprocess_run (* args , ** kwargs ):
536
- # We still use the recorder to check which commands were called
537
482
run_recorder (* args , ** kwargs )
538
-
539
- # The command is the first positional argument, e.g., ['kubectl', 'apply', ...]
540
483
command_list = args [0 ]
541
-
542
- # Check if this is the 'kubectl apply' call
543
484
if command_list and command_list [0 :2 ] == ["kubectl" , "apply" ]:
544
- # If it is, return a fake process object with a .stdout attribute
545
- # This mimics the real output from kubectl.
546
485
fake_stdout = "deployment.apps/gke-svc created\n service/gke-svc created"
547
486
return types .SimpleNamespace (stdout = fake_stdout )
548
-
549
- # For all other subprocess.run calls (like 'gcloud builds submit'),
550
- # we don't need a return value, so the default None is fine.
551
487
return None
552
488
553
489
monkeypatch .setattr (subprocess , "run" , mock_subprocess_run )
554
490
monkeypatch .setattr (shutil , "rmtree" , rmtree_recorder )
555
491
556
- # Execute
557
492
cli_deploy .to_gke (
558
493
agent_folder = str (src_dir ),
559
494
project = "gke-proj" ,
@@ -572,17 +507,14 @@ def mock_subprocess_run(*args, **kwargs):
572
507
artifact_service_uri = "gs://gke-bucket" ,
573
508
)
574
509
575
- # 1. Verify Dockerfile (basic check)
576
510
dockerfile_path = tmp_path / "Dockerfile"
577
511
assert dockerfile_path .is_file ()
578
512
dockerfile_content = dockerfile_path .read_text ()
579
513
assert "CMD adk web --port=9090" in dockerfile_content
580
514
assert "RUN pip install google-adk==1.2.0" in dockerfile_content
581
515
582
- # 2. Verify command executions by checking each recorded call
583
516
assert len (run_recorder .calls ) == 3 , "Expected 3 subprocess calls"
584
517
585
- # Call 1: gcloud builds submit
586
518
build_args = run_recorder .calls [0 ][0 ][0 ]
587
519
expected_build_args = [
588
520
"gcloud" ,
@@ -596,7 +528,6 @@ def mock_subprocess_run(*args, **kwargs):
596
528
]
597
529
assert build_args == expected_build_args
598
530
599
- # Call 2: gcloud container clusters get-credentials
600
531
creds_args = run_recorder .calls [1 ][0 ][0 ]
601
532
expected_creds_args = [
602
533
"gcloud" ,
@@ -616,12 +547,10 @@ def mock_subprocess_run(*args, **kwargs):
616
547
in dockerfile_content
617
548
)
618
549
619
- # Call 3: kubectl apply
620
550
apply_args = run_recorder .calls [2 ][0 ][0 ]
621
551
expected_apply_args = ["kubectl" , "apply" , "-f" , str (tmp_path )]
622
552
assert apply_args == expected_apply_args
623
553
624
- # 3. Verify deployment.yaml content
625
554
deployment_yaml_path = tmp_path / "deployment.yaml"
626
555
assert deployment_yaml_path .is_file ()
627
556
yaml_content = deployment_yaml_path .read_text ()
0 commit comments