|
22 | 22 | import shutil |
23 | 23 | import subprocess |
24 | 24 | import sys |
25 | | -import tempfile |
26 | 25 | import types |
27 | 26 | from typing import Any |
28 | 27 | from typing import Callable |
@@ -217,179 +216,6 @@ def test_get_service_option_by_adk_version( |
217 | 216 | assert actual.rstrip() == expected.rstrip() |
218 | 217 |
|
219 | 218 |
|
220 | | -@pytest.mark.parametrize("include_requirements", [True, False]) |
221 | | -@pytest.mark.parametrize("with_ui", [True, False]) |
222 | | -def test_to_cloud_run_happy_path( |
223 | | - monkeypatch: pytest.MonkeyPatch, |
224 | | - agent_dir: Callable[[bool, bool], Path], |
225 | | - tmp_path: Path, |
226 | | - include_requirements: bool, |
227 | | - with_ui: bool, |
228 | | -) -> None: |
229 | | - """ |
230 | | - End-to-end execution test for `to_cloud_run`. |
231 | | - """ |
232 | | - src_dir = agent_dir(include_requirements, False) |
233 | | - run_recorder = _Recorder() |
234 | | - |
235 | | - monkeypatch.setattr(subprocess, "run", run_recorder) |
236 | | - rmtree_recorder = _Recorder() |
237 | | - monkeypatch.setattr(shutil, "rmtree", rmtree_recorder) |
238 | | - |
239 | | - cli_deploy.to_cloud_run( |
240 | | - agent_folder=str(src_dir), |
241 | | - project="proj", |
242 | | - region="asia-northeast1", |
243 | | - service_name="svc", |
244 | | - app_name="agent", |
245 | | - temp_folder=str(tmp_path), |
246 | | - port=8080, |
247 | | - trace_to_cloud=True, |
248 | | - with_ui=with_ui, |
249 | | - log_level="info", |
250 | | - verbosity="info", |
251 | | - allow_origins=["http://localhost:3000", "https://my-app.com"], |
252 | | - session_service_uri="sqlite://", |
253 | | - artifact_service_uri="gs://bucket", |
254 | | - memory_service_uri="rag://", |
255 | | - adk_version="1.3.0", |
256 | | - ) |
257 | | - |
258 | | - agent_dest_path = tmp_path / "agents" / "agent" |
259 | | - assert (agent_dest_path / "agent.py").is_file() |
260 | | - assert (agent_dest_path / "__init__.py").is_file() |
261 | | - assert ( |
262 | | - agent_dest_path / "requirements.txt" |
263 | | - ).is_file() == include_requirements |
264 | | - |
265 | | - dockerfile_path = tmp_path / "Dockerfile" |
266 | | - assert dockerfile_path.is_file() |
267 | | - dockerfile_content = dockerfile_path.read_text() |
268 | | - |
269 | | - expected_command = "web" if with_ui else "api_server" |
270 | | - assert f"CMD adk {expected_command} --port=8080" in dockerfile_content |
271 | | - assert "FROM python:3.11-slim" in dockerfile_content |
272 | | - assert ( |
273 | | - 'RUN adduser --disabled-password --gecos "" myuser' in dockerfile_content |
274 | | - ) |
275 | | - assert "USER myuser" in dockerfile_content |
276 | | - assert "ENV GOOGLE_CLOUD_PROJECT=proj" in dockerfile_content |
277 | | - assert "ENV GOOGLE_CLOUD_LOCATION=asia-northeast1" in dockerfile_content |
278 | | - assert "RUN pip install google-adk==1.3.0" in dockerfile_content |
279 | | - assert "--trace_to_cloud" in dockerfile_content |
280 | | - |
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 |
285 | | - |
286 | | - assert ( |
287 | | - "--allow_origins=http://localhost:3000,https://my-app.com" |
288 | | - in dockerfile_content |
289 | | - ) |
290 | | - |
291 | | - assert len(run_recorder.calls) == 1 |
292 | | - gcloud_args = run_recorder.get_last_call_args()[0] |
293 | | - |
294 | | - expected_gcloud_command = [ |
295 | | - "gcloud", |
296 | | - "run", |
297 | | - "deploy", |
298 | | - "svc", |
299 | | - "--source", |
300 | | - str(tmp_path), |
301 | | - "--project", |
302 | | - "proj", |
303 | | - "--region", |
304 | | - "asia-northeast1", |
305 | | - "--port", |
306 | | - "8080", |
307 | | - "--verbosity", |
308 | | - "info", |
309 | | - "--labels", |
310 | | - "created-by=adk", |
311 | | - ] |
312 | | - assert gcloud_args == expected_gcloud_command |
313 | | - |
314 | | - assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_path) |
315 | | - |
316 | | - |
317 | | -def test_to_cloud_run_cleans_temp_dir( |
318 | | - monkeypatch: pytest.MonkeyPatch, |
319 | | - agent_dir: Callable[[bool, bool], Path], |
320 | | -) -> None: |
321 | | - """`to_cloud_run` should always delete the temporary folder on exit.""" |
322 | | - tmp_dir = Path(tempfile.mkdtemp()) |
323 | | - src_dir = agent_dir(False, False) |
324 | | - |
325 | | - deleted: Dict[str, Path] = {} |
326 | | - |
327 | | - def _fake_rmtree(path: str | Path, *a: Any, **k: Any) -> None: |
328 | | - deleted["path"] = Path(path) |
329 | | - |
330 | | - monkeypatch.setattr(shutil, "rmtree", _fake_rmtree) |
331 | | - monkeypatch.setattr(subprocess, "run", _Recorder()) |
332 | | - |
333 | | - cli_deploy.to_cloud_run( |
334 | | - agent_folder=str(src_dir), |
335 | | - project="proj", |
336 | | - region=None, |
337 | | - service_name="svc", |
338 | | - app_name="app", |
339 | | - temp_folder=str(tmp_dir), |
340 | | - port=8080, |
341 | | - trace_to_cloud=False, |
342 | | - with_ui=False, |
343 | | - log_level="info", |
344 | | - verbosity="info", |
345 | | - adk_version="1.0.0", |
346 | | - session_service_uri=None, |
347 | | - artifact_service_uri=None, |
348 | | - memory_service_uri=None, |
349 | | - ) |
350 | | - |
351 | | - assert deleted["path"] == tmp_dir |
352 | | - |
353 | | - |
354 | | -def test_to_cloud_run_cleans_temp_dir_on_failure( |
355 | | - monkeypatch: pytest.MonkeyPatch, |
356 | | - agent_dir: Callable[[bool, bool], Path], |
357 | | -) -> None: |
358 | | - """`to_cloud_run` should delete the temp folder on exit, even if gcloud fails.""" |
359 | | - tmp_dir = Path(tempfile.mkdtemp()) |
360 | | - src_dir = agent_dir(False, False) |
361 | | - |
362 | | - rmtree_recorder = _Recorder() |
363 | | - monkeypatch.setattr(shutil, "rmtree", rmtree_recorder) |
364 | | - monkeypatch.setattr( |
365 | | - subprocess, |
366 | | - "run", |
367 | | - mock.Mock(side_effect=subprocess.CalledProcessError(1, "gcloud")), |
368 | | - ) |
369 | | - |
370 | | - with pytest.raises(subprocess.CalledProcessError): |
371 | | - cli_deploy.to_cloud_run( |
372 | | - agent_folder=str(src_dir), |
373 | | - project="proj", |
374 | | - region="us-central1", |
375 | | - service_name="svc", |
376 | | - app_name="app", |
377 | | - temp_folder=str(tmp_dir), |
378 | | - port=8080, |
379 | | - trace_to_cloud=False, |
380 | | - with_ui=False, |
381 | | - log_level="info", |
382 | | - verbosity="info", |
383 | | - adk_version="1.0.0", |
384 | | - session_service_uri=None, |
385 | | - artifact_service_uri=None, |
386 | | - memory_service_uri=None, |
387 | | - ) |
388 | | - |
389 | | - assert rmtree_recorder.calls, "shutil.rmtree should have been called" |
390 | | - assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_dir) |
391 | | - |
392 | | - |
393 | 219 | @pytest.mark.usefixtures("mock_vertex_ai") |
394 | 220 | @pytest.mark.parametrize("has_reqs", [True, False]) |
395 | 221 | @pytest.mark.parametrize("has_env", [True, False]) |
@@ -565,76 +391,3 @@ def mock_subprocess_run(*args, **kwargs): |
565 | 391 |
|
566 | 392 | # 4. Verify cleanup |
567 | 393 | assert str(rmtree_recorder.get_last_call_args()[0]) == str(tmp_path) |
568 | | - |
569 | | - |
570 | | -# Label merging tests |
571 | | -@pytest.mark.parametrize( |
572 | | - "extra_gcloud_args, expected_labels", |
573 | | - [ |
574 | | - # No user labels - should only have default ADK label |
575 | | - (None, "created-by=adk"), |
576 | | - ([], "created-by=adk"), |
577 | | - # Single user label |
578 | | - (["--labels=env=test"], "created-by=adk,env=test"), |
579 | | - # Multiple user labels in same argument |
580 | | - ( |
581 | | - ["--labels=env=test,team=myteam"], |
582 | | - "created-by=adk,env=test,team=myteam", |
583 | | - ), |
584 | | - # User labels mixed with other args |
585 | | - ( |
586 | | - ["--memory=1Gi", "--labels=env=test", "--cpu=1"], |
587 | | - "created-by=adk,env=test", |
588 | | - ), |
589 | | - # Multiple --labels arguments |
590 | | - ( |
591 | | - ["--labels=env=test", "--labels=team=myteam"], |
592 | | - "created-by=adk,env=test,team=myteam", |
593 | | - ), |
594 | | - # Labels with other passthrough args |
595 | | - ( |
596 | | - ["--timeout=300", "--labels=env=prod", "--max-instances=10"], |
597 | | - "created-by=adk,env=prod", |
598 | | - ), |
599 | | - ], |
600 | | -) |
601 | | -def test_cloud_run_label_merging( |
602 | | - monkeypatch: pytest.MonkeyPatch, |
603 | | - agent_dir: Callable[[bool, bool], Path], |
604 | | - tmp_path: Path, |
605 | | - extra_gcloud_args: list[str] | None, |
606 | | - expected_labels: str, |
607 | | -) -> None: |
608 | | - """Test that user labels are properly merged with the default ADK label.""" |
609 | | - src_dir = agent_dir(False, False) |
610 | | - run_recorder = _Recorder() |
611 | | - |
612 | | - monkeypatch.setattr(subprocess, "run", run_recorder) |
613 | | - monkeypatch.setattr(shutil, "rmtree", lambda x: None) |
614 | | - |
615 | | - # Execute the function under test |
616 | | - cli_deploy.to_cloud_run( |
617 | | - agent_folder=str(src_dir), |
618 | | - project="test-project", |
619 | | - region="us-central1", |
620 | | - service_name="test-service", |
621 | | - app_name="test-app", |
622 | | - temp_folder=str(tmp_path), |
623 | | - port=8080, |
624 | | - trace_to_cloud=False, |
625 | | - with_ui=False, |
626 | | - log_level="info", |
627 | | - verbosity="info", |
628 | | - adk_version="1.0.0", |
629 | | - extra_gcloud_args=tuple(extra_gcloud_args) if extra_gcloud_args else None, |
630 | | - ) |
631 | | - |
632 | | - # Verify that the gcloud command was called |
633 | | - assert len(run_recorder.calls) == 1 |
634 | | - gcloud_args = run_recorder.get_last_call_args()[0] |
635 | | - |
636 | | - # Find the labels argument |
637 | | - labels_idx = gcloud_args.index("--labels") |
638 | | - actual_labels = gcloud_args[labels_idx + 1] |
639 | | - |
640 | | - assert actual_labels == expected_labels |
0 commit comments