mirror of
https://github.com/EvolutionAPI/adk-python.git
synced 2025-07-13 15:14:50 -06:00
chore: reformat the codes using autoformat.sh
PiperOrigin-RevId: 762004002
This commit is contained in:
parent
a2263b1808
commit
ff8a3c9b43
@ -21,6 +21,7 @@ from typing import List
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
from unittest import mock
|
||||
|
||||
from google.adk.agents.base_agent import BaseAgent
|
||||
from google.adk.agents.callback_context import CallbackContext
|
||||
from google.adk.agents.invocation_context import InvocationContext
|
||||
@ -30,6 +31,7 @@ from google.genai import types
|
||||
import pytest
|
||||
import pytest_mock
|
||||
from typing_extensions import override
|
||||
|
||||
from .. import testing_utils
|
||||
|
||||
|
||||
|
@ -1,7 +1,11 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock, AsyncMock, patch
|
||||
from google.adk.agents.live_request_queue import LiveRequest, LiveRequestQueue
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from google.adk.agents.live_request_queue import LiveRequest
|
||||
from google.adk.agents.live_request_queue import LiveRequestQueue
|
||||
from google.genai import types
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
@ -15,7 +15,8 @@
|
||||
"""Unit tests for canonical_xxx fields in LlmAgent."""
|
||||
|
||||
from typing import Any
|
||||
from typing import Optional, cast
|
||||
from typing import cast
|
||||
from typing import Optional
|
||||
|
||||
from google.adk.agents.callback_context import CallbackContext
|
||||
from google.adk.agents.invocation_context import InvocationContext
|
||||
@ -146,6 +147,7 @@ async def test_canonical_global_instruction():
|
||||
assert canonical_global_instruction == 'global instruction: state_value'
|
||||
assert bypass_state_injection
|
||||
|
||||
|
||||
async def test_async_canonical_global_instruction():
|
||||
async def _global_instruction_provider(ctx: ReadonlyContext) -> str:
|
||||
return f'global instruction: {ctx.state["state_var"]}'
|
||||
|
@ -1,7 +1,8 @@
|
||||
import pytest
|
||||
from unittest.mock import MagicMock
|
||||
from types import MappingProxyType
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from google.adk.agents.readonly_context import ReadonlyContext
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -1,8 +1,10 @@
|
||||
import pytest
|
||||
import sys
|
||||
import logging
|
||||
from unittest.mock import patch, ANY
|
||||
import sys
|
||||
from unittest.mock import ANY
|
||||
from unittest.mock import patch
|
||||
|
||||
from google.adk.agents.run_config import RunConfig
|
||||
import pytest
|
||||
|
||||
|
||||
def test_validate_max_llm_calls_valid():
|
||||
|
@ -17,12 +17,11 @@
|
||||
import enum
|
||||
from typing import Optional
|
||||
from typing import Union
|
||||
from unittest import mock
|
||||
|
||||
from google.adk.artifacts import GcsArtifactService
|
||||
from google.adk.artifacts import InMemoryArtifactService
|
||||
from google.genai import types
|
||||
|
||||
from unittest import mock
|
||||
import pytest
|
||||
|
||||
Enum = enum.Enum
|
||||
|
@ -15,19 +15,18 @@
|
||||
import copy
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
from fastapi.openapi.models import APIKey
|
||||
from fastapi.openapi.models import APIKeyIn
|
||||
from fastapi.openapi.models import OAuth2
|
||||
from fastapi.openapi.models import OAuthFlowAuthorizationCode
|
||||
from fastapi.openapi.models import OAuthFlows
|
||||
|
||||
from google.adk.auth.auth_credential import AuthCredential
|
||||
from google.adk.auth.auth_credential import AuthCredentialTypes
|
||||
from google.adk.auth.auth_credential import OAuth2Auth
|
||||
from google.adk.auth.auth_handler import AuthHandler
|
||||
from google.adk.auth.auth_schemes import OpenIdConnectWithConfig
|
||||
from google.adk.auth.auth_tool import AuthConfig
|
||||
import pytest
|
||||
|
||||
|
||||
# Mock classes for testing
|
||||
|
@ -16,182 +16,195 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
import json
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import types
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
import click
|
||||
import google.adk.cli.cli as cli
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple
|
||||
|
||||
# Helpers
|
||||
class _Recorder:
|
||||
"""Callable that records every invocation."""
|
||||
"""Callable that records every invocation."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.calls: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
||||
def __init__(self) -> None:
|
||||
self.calls: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.calls.append((args, kwargs))
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.calls.append((args, kwargs))
|
||||
|
||||
|
||||
# Fixtures
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mute_click(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Silence click output in every test."""
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
monkeypatch.setattr(click, "secho", lambda *a, **k: None)
|
||||
"""Silence click output in every test."""
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
monkeypatch.setattr(click, "secho", lambda *a, **k: None)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _patch_types_and_runner(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Replace google.genai.types and Runner with lightweight fakes."""
|
||||
"""Replace google.genai.types and Runner with lightweight fakes."""
|
||||
|
||||
# Dummy Part / Content
|
||||
class _Part:
|
||||
def __init__(self, text: str | None = "") -> None:
|
||||
self.text = text
|
||||
# Dummy Part / Content
|
||||
class _Part:
|
||||
|
||||
class _Content:
|
||||
def __init__(self, role: str, parts: List[_Part]) -> None:
|
||||
self.role = role
|
||||
self.parts = parts
|
||||
def __init__(self, text: str | None = "") -> None:
|
||||
self.text = text
|
||||
|
||||
monkeypatch.setattr(cli.types, "Part", _Part)
|
||||
monkeypatch.setattr(cli.types, "Content", _Content)
|
||||
class _Content:
|
||||
|
||||
# Fake Runner yielding a single assistant echo
|
||||
class _FakeRunner:
|
||||
def __init__(self, *a: Any, **k: Any) -> None: ...
|
||||
def __init__(self, role: str, parts: List[_Part]) -> None:
|
||||
self.role = role
|
||||
self.parts = parts
|
||||
|
||||
async def run_async(self, *a: Any, **k: Any):
|
||||
message = a[2] if len(a) >= 3 else k["new_message"]
|
||||
text = message.parts[0].text if message.parts else ""
|
||||
response = _Content("assistant", [_Part(f"echo:{text}")])
|
||||
yield types.SimpleNamespace(author="assistant", content=response)
|
||||
monkeypatch.setattr(cli.types, "Part", _Part)
|
||||
monkeypatch.setattr(cli.types, "Content", _Content)
|
||||
|
||||
monkeypatch.setattr(cli, "Runner", _FakeRunner)
|
||||
# Fake Runner yielding a single assistant echo
|
||||
class _FakeRunner:
|
||||
|
||||
def __init__(self, *a: Any, **k: Any) -> None:
|
||||
...
|
||||
|
||||
async def run_async(self, *a: Any, **k: Any):
|
||||
message = a[2] if len(a) >= 3 else k["new_message"]
|
||||
text = message.parts[0].text if message.parts else ""
|
||||
response = _Content("assistant", [_Part(f"echo:{text}")])
|
||||
yield types.SimpleNamespace(author="assistant", content=response)
|
||||
|
||||
monkeypatch.setattr(cli, "Runner", _FakeRunner)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def fake_agent(tmp_path: Path, monkeypatch: pytest.MonkeyPatch):
|
||||
"""Create a minimal importable agent package and patch importlib."""
|
||||
"""Create a minimal importable agent package and patch importlib."""
|
||||
|
||||
parent_dir = tmp_path / "agents"
|
||||
parent_dir.mkdir()
|
||||
agent_dir = parent_dir / "fake_agent"
|
||||
agent_dir.mkdir()
|
||||
# __init__.py exposes root_agent with .name
|
||||
(agent_dir / "__init__.py").write_text(
|
||||
"from types import SimpleNamespace\n"
|
||||
"root_agent = SimpleNamespace(name='fake_root')\n"
|
||||
)
|
||||
parent_dir = tmp_path / "agents"
|
||||
parent_dir.mkdir()
|
||||
agent_dir = parent_dir / "fake_agent"
|
||||
agent_dir.mkdir()
|
||||
# __init__.py exposes root_agent with .name
|
||||
(agent_dir / "__init__.py").write_text(
|
||||
"from types import SimpleNamespace\n"
|
||||
"root_agent = SimpleNamespace(name='fake_root')\n"
|
||||
)
|
||||
|
||||
# Ensure importable via sys.path
|
||||
sys.path.insert(0, str(parent_dir))
|
||||
# Ensure importable via sys.path
|
||||
sys.path.insert(0, str(parent_dir))
|
||||
|
||||
import importlib
|
||||
import importlib
|
||||
|
||||
module = importlib.import_module("fake_agent")
|
||||
fake_module = types.SimpleNamespace(agent=module)
|
||||
module = importlib.import_module("fake_agent")
|
||||
fake_module = types.SimpleNamespace(agent=module)
|
||||
|
||||
monkeypatch.setattr(importlib, "import_module", lambda n: fake_module)
|
||||
monkeypatch.setattr(cli.envs, "load_dotenv_for_agent", lambda *a, **k: None)
|
||||
monkeypatch.setattr(importlib, "import_module", lambda n: fake_module)
|
||||
monkeypatch.setattr(cli.envs, "load_dotenv_for_agent", lambda *a, **k: None)
|
||||
|
||||
yield parent_dir, "fake_agent"
|
||||
yield parent_dir, "fake_agent"
|
||||
|
||||
# Cleanup
|
||||
sys.path.remove(str(parent_dir))
|
||||
del sys.modules["fake_agent"]
|
||||
# Cleanup
|
||||
sys.path.remove(str(parent_dir))
|
||||
del sys.modules["fake_agent"]
|
||||
|
||||
|
||||
# _run_input_file
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_input_file_outputs(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""run_input_file should echo user & assistant messages and return a populated session."""
|
||||
recorder: List[str] = []
|
||||
async def test_run_input_file_outputs(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""run_input_file should echo user & assistant messages and return a populated session."""
|
||||
recorder: List[str] = []
|
||||
|
||||
def _echo(msg: str) -> None:
|
||||
recorder.append(msg)
|
||||
def _echo(msg: str) -> None:
|
||||
recorder.append(msg)
|
||||
|
||||
monkeypatch.setattr(click, "echo", _echo)
|
||||
monkeypatch.setattr(click, "echo", _echo)
|
||||
|
||||
input_json = {
|
||||
"state": {"foo": "bar"},
|
||||
"queries": ["hello world"],
|
||||
}
|
||||
input_path = tmp_path / "input.json"
|
||||
input_path.write_text(json.dumps(input_json))
|
||||
input_json = {
|
||||
"state": {"foo": "bar"},
|
||||
"queries": ["hello world"],
|
||||
}
|
||||
input_path = tmp_path / "input.json"
|
||||
input_path.write_text(json.dumps(input_json))
|
||||
|
||||
artifact_service = cli.InMemoryArtifactService()
|
||||
session_service = cli.InMemorySessionService()
|
||||
dummy_root = types.SimpleNamespace(name="root")
|
||||
artifact_service = cli.InMemoryArtifactService()
|
||||
session_service = cli.InMemorySessionService()
|
||||
dummy_root = types.SimpleNamespace(name="root")
|
||||
|
||||
session = await cli.run_input_file(
|
||||
app_name="app",
|
||||
user_id="user",
|
||||
root_agent=dummy_root,
|
||||
artifact_service=artifact_service,
|
||||
session_service=session_service,
|
||||
input_path=str(input_path),
|
||||
)
|
||||
session = await cli.run_input_file(
|
||||
app_name="app",
|
||||
user_id="user",
|
||||
root_agent=dummy_root,
|
||||
artifact_service=artifact_service,
|
||||
session_service=session_service,
|
||||
input_path=str(input_path),
|
||||
)
|
||||
|
||||
assert session.state["foo"] == "bar"
|
||||
assert any("[user]:" in line for line in recorder)
|
||||
assert any("[assistant]:" in line for line in recorder)
|
||||
assert session.state["foo"] == "bar"
|
||||
assert any("[user]:" in line for line in recorder)
|
||||
assert any("[assistant]:" in line for line in recorder)
|
||||
|
||||
|
||||
# _run_cli (input_file branch)
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_cli_with_input_file(fake_agent, tmp_path: Path) -> None:
|
||||
"""run_cli should process an input file without raising and without saving."""
|
||||
parent_dir, folder_name = fake_agent
|
||||
input_json = {"state": {}, "queries": ["ping"]}
|
||||
input_path = tmp_path / "in.json"
|
||||
input_path.write_text(json.dumps(input_json))
|
||||
"""run_cli should process an input file without raising and without saving."""
|
||||
parent_dir, folder_name = fake_agent
|
||||
input_json = {"state": {}, "queries": ["ping"]}
|
||||
input_path = tmp_path / "in.json"
|
||||
input_path.write_text(json.dumps(input_json))
|
||||
|
||||
await cli.run_cli(
|
||||
agent_parent_dir=str(parent_dir),
|
||||
agent_folder_name=folder_name,
|
||||
input_file=str(input_path),
|
||||
saved_session_file=None,
|
||||
save_session=False,
|
||||
)
|
||||
await cli.run_cli(
|
||||
agent_parent_dir=str(parent_dir),
|
||||
agent_folder_name=folder_name,
|
||||
input_file=str(input_path),
|
||||
saved_session_file=None,
|
||||
save_session=False,
|
||||
)
|
||||
|
||||
|
||||
# _run_cli (interactive + save session branch)
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_cli_save_session(fake_agent, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""run_cli should save a session file when save_session=True."""
|
||||
parent_dir, folder_name = fake_agent
|
||||
async def test_run_cli_save_session(
|
||||
fake_agent, tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""run_cli should save a session file when save_session=True."""
|
||||
parent_dir, folder_name = fake_agent
|
||||
|
||||
# Simulate user typing 'exit' followed by session id 'sess123'
|
||||
responses = iter(["exit", "sess123"])
|
||||
monkeypatch.setattr("builtins.input", lambda *_a, **_k: next(responses))
|
||||
# Simulate user typing 'exit' followed by session id 'sess123'
|
||||
responses = iter(["exit", "sess123"])
|
||||
monkeypatch.setattr("builtins.input", lambda *_a, **_k: next(responses))
|
||||
|
||||
session_file = Path(parent_dir) / folder_name / "sess123.session.json"
|
||||
if session_file.exists():
|
||||
session_file.unlink()
|
||||
session_file = Path(parent_dir) / folder_name / "sess123.session.json"
|
||||
if session_file.exists():
|
||||
session_file.unlink()
|
||||
|
||||
await cli.run_cli(
|
||||
agent_parent_dir=str(parent_dir),
|
||||
agent_folder_name=folder_name,
|
||||
input_file=None,
|
||||
saved_session_file=None,
|
||||
save_session=True,
|
||||
)
|
||||
await cli.run_cli(
|
||||
agent_parent_dir=str(parent_dir),
|
||||
agent_folder_name=folder_name,
|
||||
input_file=None,
|
||||
saved_session_file=None,
|
||||
save_session=True,
|
||||
)
|
||||
|
||||
assert session_file.exists()
|
||||
data = json.loads(session_file.read_text())
|
||||
# The saved JSON should at least contain id and events keys
|
||||
assert "id" in data and "events" in data
|
||||
assert session_file.exists()
|
||||
data = json.loads(session_file.read_text())
|
||||
# The saved JSON should at least contain id and events keys
|
||||
assert "id" in data and "events" in data
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_run_interactively_whitespace_and_exit(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
async def test_run_interactively_whitespace_and_exit(
|
||||
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||
) -> None:
|
||||
"""run_interactively should skip blank input, echo once, then exit."""
|
||||
# make a session that belongs to dummy agent
|
||||
svc = cli.InMemorySessionService()
|
||||
|
@ -17,214 +17,239 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
import os
|
||||
import pytest
|
||||
import subprocess
|
||||
|
||||
import google.adk.cli.cli_create as cli_create
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Tuple
|
||||
import subprocess
|
||||
from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
|
||||
import click
|
||||
import google.adk.cli.cli_create as cli_create
|
||||
import pytest
|
||||
|
||||
|
||||
# Helpers
|
||||
class _Recorder:
|
||||
"""A callable object that records every invocation."""
|
||||
"""A callable object that records every invocation."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.calls: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
||||
def __init__(self) -> None:
|
||||
self.calls: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: D401
|
||||
self.calls.append((args, kwargs))
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> None: # noqa: D401
|
||||
self.calls.append((args, kwargs))
|
||||
|
||||
|
||||
# Fixtures
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mute_click(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Silence click output in every test."""
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
monkeypatch.setattr(click, "secho", lambda *a, **k: None)
|
||||
"""Silence click output in every test."""
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
monkeypatch.setattr(click, "secho", lambda *a, **k: None)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def agent_folder(tmp_path: Path) -> Path:
|
||||
"""Return a temporary path that will hold generated agent sources."""
|
||||
return tmp_path / "agent"
|
||||
"""Return a temporary path that will hold generated agent sources."""
|
||||
return tmp_path / "agent"
|
||||
|
||||
|
||||
# _generate_files
|
||||
def test_generate_files_with_api_key(agent_folder: Path) -> None:
|
||||
"""Files should be created with the API-key backend and correct .env flags."""
|
||||
cli_create._generate_files(
|
||||
str(agent_folder),
|
||||
google_api_key="dummy-key",
|
||||
model="gemini-2.0-flash-001",
|
||||
)
|
||||
"""Files should be created with the API-key backend and correct .env flags."""
|
||||
cli_create._generate_files(
|
||||
str(agent_folder),
|
||||
google_api_key="dummy-key",
|
||||
model="gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
env_content = (agent_folder / ".env").read_text()
|
||||
assert "GOOGLE_API_KEY=dummy-key" in env_content
|
||||
assert "GOOGLE_GENAI_USE_VERTEXAI=0" in env_content
|
||||
assert (agent_folder / "agent.py").exists()
|
||||
assert (agent_folder / "__init__.py").exists()
|
||||
env_content = (agent_folder / ".env").read_text()
|
||||
assert "GOOGLE_API_KEY=dummy-key" in env_content
|
||||
assert "GOOGLE_GENAI_USE_VERTEXAI=0" in env_content
|
||||
assert (agent_folder / "agent.py").exists()
|
||||
assert (agent_folder / "__init__.py").exists()
|
||||
|
||||
|
||||
def test_generate_files_with_gcp(agent_folder: Path) -> None:
|
||||
"""Files should be created with Vertex AI backend and correct .env flags."""
|
||||
cli_create._generate_files(
|
||||
str(agent_folder),
|
||||
google_cloud_project="proj",
|
||||
google_cloud_region="us-central1",
|
||||
model="gemini-2.0-flash-001",
|
||||
)
|
||||
"""Files should be created with Vertex AI backend and correct .env flags."""
|
||||
cli_create._generate_files(
|
||||
str(agent_folder),
|
||||
google_cloud_project="proj",
|
||||
google_cloud_region="us-central1",
|
||||
model="gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
env_content = (agent_folder / ".env").read_text()
|
||||
assert "GOOGLE_CLOUD_PROJECT=proj" in env_content
|
||||
assert "GOOGLE_CLOUD_LOCATION=us-central1" in env_content
|
||||
assert "GOOGLE_GENAI_USE_VERTEXAI=1" in env_content
|
||||
env_content = (agent_folder / ".env").read_text()
|
||||
assert "GOOGLE_CLOUD_PROJECT=proj" in env_content
|
||||
assert "GOOGLE_CLOUD_LOCATION=us-central1" in env_content
|
||||
assert "GOOGLE_GENAI_USE_VERTEXAI=1" in env_content
|
||||
|
||||
|
||||
def test_generate_files_overwrite(agent_folder: Path) -> None:
|
||||
"""Existing files should be overwritten when generating again."""
|
||||
agent_folder.mkdir(parents=True, exist_ok=True)
|
||||
(agent_folder / ".env").write_text("OLD")
|
||||
"""Existing files should be overwritten when generating again."""
|
||||
agent_folder.mkdir(parents=True, exist_ok=True)
|
||||
(agent_folder / ".env").write_text("OLD")
|
||||
|
||||
cli_create._generate_files(
|
||||
str(agent_folder),
|
||||
google_api_key="new-key",
|
||||
model="gemini-2.0-flash-001",
|
||||
)
|
||||
cli_create._generate_files(
|
||||
str(agent_folder),
|
||||
google_api_key="new-key",
|
||||
model="gemini-2.0-flash-001",
|
||||
)
|
||||
|
||||
assert "GOOGLE_API_KEY=new-key" in (agent_folder / ".env").read_text()
|
||||
assert "GOOGLE_API_KEY=new-key" in (agent_folder / ".env").read_text()
|
||||
|
||||
|
||||
def test_generate_files_permission_error(monkeypatch: pytest.MonkeyPatch, agent_folder: Path) -> None:
|
||||
"""PermissionError raised by os.makedirs should propagate."""
|
||||
monkeypatch.setattr(os, "makedirs", lambda *a, **k: (_ for _ in ()).throw(PermissionError()))
|
||||
with pytest.raises(PermissionError):
|
||||
cli_create._generate_files(str(agent_folder), model="gemini-2.0-flash-001")
|
||||
def test_generate_files_permission_error(
|
||||
monkeypatch: pytest.MonkeyPatch, agent_folder: Path
|
||||
) -> None:
|
||||
"""PermissionError raised by os.makedirs should propagate."""
|
||||
monkeypatch.setattr(
|
||||
os, "makedirs", lambda *a, **k: (_ for _ in ()).throw(PermissionError())
|
||||
)
|
||||
with pytest.raises(PermissionError):
|
||||
cli_create._generate_files(str(agent_folder), model="gemini-2.0-flash-001")
|
||||
|
||||
|
||||
def test_generate_files_no_params(agent_folder: Path) -> None:
|
||||
"""No backend parameters → minimal .env file is generated."""
|
||||
cli_create._generate_files(str(agent_folder), model="gemini-2.0-flash-001")
|
||||
"""No backend parameters → minimal .env file is generated."""
|
||||
cli_create._generate_files(str(agent_folder), model="gemini-2.0-flash-001")
|
||||
|
||||
env_content = (agent_folder / ".env").read_text()
|
||||
for key in ("GOOGLE_API_KEY", "GOOGLE_CLOUD_PROJECT", "GOOGLE_CLOUD_LOCATION", "GOOGLE_GENAI_USE_VERTEXAI"):
|
||||
assert key not in env_content
|
||||
env_content = (agent_folder / ".env").read_text()
|
||||
for key in (
|
||||
"GOOGLE_API_KEY",
|
||||
"GOOGLE_CLOUD_PROJECT",
|
||||
"GOOGLE_CLOUD_LOCATION",
|
||||
"GOOGLE_GENAI_USE_VERTEXAI",
|
||||
):
|
||||
assert key not in env_content
|
||||
|
||||
|
||||
# run_cmd
|
||||
def test_run_cmd_overwrite_reject(monkeypatch: pytest.MonkeyPatch, tmp_path: Path) -> None:
|
||||
"""User rejecting overwrite should trigger click.Abort."""
|
||||
agent_name = "agent"
|
||||
agent_dir = tmp_path / agent_name
|
||||
agent_dir.mkdir()
|
||||
(agent_dir / "dummy.txt").write_text("dummy")
|
||||
def test_run_cmd_overwrite_reject(
|
||||
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
||||
) -> None:
|
||||
"""User rejecting overwrite should trigger click.Abort."""
|
||||
agent_name = "agent"
|
||||
agent_dir = tmp_path / agent_name
|
||||
agent_dir.mkdir()
|
||||
(agent_dir / "dummy.txt").write_text("dummy")
|
||||
|
||||
monkeypatch.setattr(os, "getcwd", lambda: str(tmp_path))
|
||||
monkeypatch.setattr(os.path, "exists", lambda _p: True)
|
||||
monkeypatch.setattr(os, "listdir", lambda _p: ["dummy.txt"])
|
||||
monkeypatch.setattr(click, "confirm", lambda *a, **k: False)
|
||||
monkeypatch.setattr(os, "getcwd", lambda: str(tmp_path))
|
||||
monkeypatch.setattr(os.path, "exists", lambda _p: True)
|
||||
monkeypatch.setattr(os, "listdir", lambda _p: ["dummy.txt"])
|
||||
monkeypatch.setattr(click, "confirm", lambda *a, **k: False)
|
||||
|
||||
with pytest.raises(click.Abort):
|
||||
cli_create.run_cmd(
|
||||
agent_name,
|
||||
model="gemini-2.0-flash-001",
|
||||
google_api_key=None,
|
||||
google_cloud_project=None,
|
||||
google_cloud_region=None,
|
||||
)
|
||||
with pytest.raises(click.Abort):
|
||||
cli_create.run_cmd(
|
||||
agent_name,
|
||||
model="gemini-2.0-flash-001",
|
||||
google_api_key=None,
|
||||
google_cloud_project=None,
|
||||
google_cloud_region=None,
|
||||
)
|
||||
|
||||
|
||||
# Prompt helpers
|
||||
def test_prompt_for_google_cloud(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Prompt should return the project input."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "test-proj")
|
||||
assert cli_create._prompt_for_google_cloud(None) == "test-proj"
|
||||
"""Prompt should return the project input."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "test-proj")
|
||||
assert cli_create._prompt_for_google_cloud(None) == "test-proj"
|
||||
|
||||
|
||||
def test_prompt_for_google_cloud_region(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Prompt should return the region input."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "asia-northeast1")
|
||||
assert cli_create._prompt_for_google_cloud_region(None) == "asia-northeast1"
|
||||
def test_prompt_for_google_cloud_region(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Prompt should return the region input."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "asia-northeast1")
|
||||
assert cli_create._prompt_for_google_cloud_region(None) == "asia-northeast1"
|
||||
|
||||
|
||||
def test_prompt_for_google_api_key(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Prompt should return the API-key input."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "api-key")
|
||||
assert cli_create._prompt_for_google_api_key(None) == "api-key"
|
||||
"""Prompt should return the API-key input."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "api-key")
|
||||
assert cli_create._prompt_for_google_api_key(None) == "api-key"
|
||||
|
||||
|
||||
def test_prompt_for_model_gemini(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Selecting option '1' should return the default Gemini model string."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "1")
|
||||
assert cli_create._prompt_for_model() == "gemini-2.0-flash-001"
|
||||
"""Selecting option '1' should return the default Gemini model string."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "1")
|
||||
assert cli_create._prompt_for_model() == "gemini-2.0-flash-001"
|
||||
|
||||
|
||||
def test_prompt_for_model_other(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Selecting option '2' should return placeholder and call secho."""
|
||||
called: Dict[str, bool] = {}
|
||||
"""Selecting option '2' should return placeholder and call secho."""
|
||||
called: Dict[str, bool] = {}
|
||||
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "2")
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "2")
|
||||
|
||||
def _fake_secho(*_a: Any, **_k: Any) -> None:
|
||||
called["secho"] = True
|
||||
|
||||
monkeypatch.setattr(click, "secho", _fake_secho)
|
||||
assert cli_create._prompt_for_model() == "<FILL_IN_MODEL>"
|
||||
assert called.get("secho") is True
|
||||
def _fake_secho(*_a: Any, **_k: Any) -> None:
|
||||
called["secho"] = True
|
||||
|
||||
monkeypatch.setattr(click, "secho", _fake_secho)
|
||||
assert cli_create._prompt_for_model() == "<FILL_IN_MODEL>"
|
||||
assert called.get("secho") is True
|
||||
|
||||
|
||||
# Backend selection helper
|
||||
def test_prompt_to_choose_backend_api(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Choosing API-key backend returns (api_key, None, None)."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "1")
|
||||
monkeypatch.setattr(cli_create, "_prompt_for_google_api_key", lambda _v: "api-key")
|
||||
"""Choosing API-key backend returns (api_key, None, None)."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "1")
|
||||
monkeypatch.setattr(
|
||||
cli_create, "_prompt_for_google_api_key", lambda _v: "api-key"
|
||||
)
|
||||
|
||||
api_key, proj, region = cli_create._prompt_to_choose_backend(None, None, None)
|
||||
assert api_key == "api-key"
|
||||
assert proj is None and region is None
|
||||
api_key, proj, region = cli_create._prompt_to_choose_backend(None, None, None)
|
||||
assert api_key == "api-key"
|
||||
assert proj is None and region is None
|
||||
|
||||
|
||||
def test_prompt_to_choose_backend_vertex(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Choosing Vertex backend returns (None, project, region)."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "2")
|
||||
monkeypatch.setattr(cli_create, "_prompt_for_google_cloud", lambda _v: "proj")
|
||||
monkeypatch.setattr(cli_create, "_prompt_for_google_cloud_region", lambda _v: "region")
|
||||
|
||||
api_key, proj, region = cli_create._prompt_to_choose_backend(None, None, None)
|
||||
assert api_key is None
|
||||
assert proj == "proj"
|
||||
assert region == "region"
|
||||
def test_prompt_to_choose_backend_vertex(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Choosing Vertex backend returns (None, project, region)."""
|
||||
monkeypatch.setattr(click, "prompt", lambda *a, **k: "2")
|
||||
monkeypatch.setattr(cli_create, "_prompt_for_google_cloud", lambda _v: "proj")
|
||||
monkeypatch.setattr(
|
||||
cli_create, "_prompt_for_google_cloud_region", lambda _v: "region"
|
||||
)
|
||||
|
||||
api_key, proj, region = cli_create._prompt_to_choose_backend(None, None, None)
|
||||
assert api_key is None
|
||||
assert proj == "proj"
|
||||
assert region == "region"
|
||||
|
||||
|
||||
# prompt_str
|
||||
def test_prompt_str_non_empty(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""_prompt_str should retry until a non-blank string is provided."""
|
||||
responses = iter(["", " ", "valid"])
|
||||
monkeypatch.setattr(click, "prompt", lambda *_a, **_k: next(responses))
|
||||
assert cli_create._prompt_str("dummy") == "valid"
|
||||
|
||||
"""_prompt_str should retry until a non-blank string is provided."""
|
||||
responses = iter(["", " ", "valid"])
|
||||
monkeypatch.setattr(click, "prompt", lambda *_a, **_k: next(responses))
|
||||
assert cli_create._prompt_str("dummy") == "valid"
|
||||
|
||||
|
||||
# gcloud fallback helpers
|
||||
def test_get_gcp_project_from_gcloud_fail(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Failure of gcloud project lookup should return empty string."""
|
||||
monkeypatch.setattr(
|
||||
subprocess,
|
||||
"run",
|
||||
lambda *_a, **_k: (_ for _ in ()).throw(FileNotFoundError()),
|
||||
)
|
||||
assert cli_create._get_gcp_project_from_gcloud() == ""
|
||||
def test_get_gcp_project_from_gcloud_fail(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""Failure of gcloud project lookup should return empty string."""
|
||||
monkeypatch.setattr(
|
||||
subprocess,
|
||||
"run",
|
||||
lambda *_a, **_k: (_ for _ in ()).throw(FileNotFoundError()),
|
||||
)
|
||||
assert cli_create._get_gcp_project_from_gcloud() == ""
|
||||
|
||||
|
||||
def test_get_gcp_region_from_gcloud_fail(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""CalledProcessError should result in empty region string."""
|
||||
monkeypatch.setattr(
|
||||
subprocess,
|
||||
"run",
|
||||
lambda *_a, **_k: (_ for _ in ()).throw(subprocess.CalledProcessError(1, "gcloud")),
|
||||
)
|
||||
assert cli_create._get_gcp_region_from_gcloud() == ""
|
||||
def test_get_gcp_region_from_gcloud_fail(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""CalledProcessError should result in empty region string."""
|
||||
monkeypatch.setattr(
|
||||
subprocess,
|
||||
"run",
|
||||
lambda *_a, **_k: (_ for _ in ()).throw(
|
||||
subprocess.CalledProcessError(1, "gcloud")
|
||||
),
|
||||
)
|
||||
assert cli_create._get_gcp_region_from_gcloud() == ""
|
||||
|
@ -17,70 +17,74 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import click
|
||||
from pathlib import Path
|
||||
import shutil
|
||||
import pytest
|
||||
import subprocess
|
||||
import tempfile
|
||||
import types
|
||||
|
||||
import google.adk.cli.cli_deploy as cli_deploy
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Any, Callable, Dict, List, Tuple
|
||||
from typing import Any
|
||||
from typing import Callable
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
from typing import Tuple
|
||||
from unittest import mock
|
||||
|
||||
import click
|
||||
import google.adk.cli.cli_deploy as cli_deploy
|
||||
import pytest
|
||||
|
||||
|
||||
# Helpers
|
||||
class _Recorder:
|
||||
"""A callable object that records every invocation."""
|
||||
"""A callable object that records every invocation."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.calls: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
||||
def __init__(self) -> None:
|
||||
self.calls: List[Tuple[Tuple[Any, ...], Dict[str, Any]]] = []
|
||||
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.calls.append((args, kwargs))
|
||||
def __call__(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.calls.append((args, kwargs))
|
||||
|
||||
|
||||
# Fixtures
|
||||
@pytest.fixture(autouse=True)
|
||||
def _mute_click(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""Suppress click.echo to keep test output clean."""
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
"""Suppress click.echo to keep test output clean."""
|
||||
monkeypatch.setattr(click, "echo", lambda *a, **k: None)
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def agent_dir(tmp_path: Path) -> Callable[[bool], Path]:
|
||||
"""Return a factory that creates a dummy agent directory tree."""
|
||||
"""Return a factory that creates a dummy agent directory tree."""
|
||||
|
||||
def _factory(include_requirements: bool) -> Path:
|
||||
base = tmp_path / "agent"
|
||||
base.mkdir()
|
||||
(base / "agent.py").write_text("# dummy agent")
|
||||
(base / "__init__.py").touch()
|
||||
if include_requirements:
|
||||
(base / "requirements.txt").write_text("pytest\n")
|
||||
return base
|
||||
def _factory(include_requirements: bool) -> Path:
|
||||
base = tmp_path / "agent"
|
||||
base.mkdir()
|
||||
(base / "agent.py").write_text("# dummy agent")
|
||||
(base / "__init__.py").touch()
|
||||
if include_requirements:
|
||||
(base / "requirements.txt").write_text("pytest\n")
|
||||
return base
|
||||
|
||||
return _factory
|
||||
return _factory
|
||||
|
||||
|
||||
# _resolve_project
|
||||
def test_resolve_project_with_option() -> None:
|
||||
"""It should return the explicit project value untouched."""
|
||||
assert cli_deploy._resolve_project("my-project") == "my-project"
|
||||
"""It should return the explicit project value untouched."""
|
||||
assert cli_deploy._resolve_project("my-project") == "my-project"
|
||||
|
||||
|
||||
def test_resolve_project_from_gcloud(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""It should fall back to `gcloud config get-value project` when no value supplied."""
|
||||
monkeypatch.setattr(
|
||||
subprocess,
|
||||
"run",
|
||||
lambda *a, **k: types.SimpleNamespace(stdout="gcp-proj\n"),
|
||||
)
|
||||
"""It should fall back to `gcloud config get-value project` when no value supplied."""
|
||||
monkeypatch.setattr(
|
||||
subprocess,
|
||||
"run",
|
||||
lambda *a, **k: types.SimpleNamespace(stdout="gcp-proj\n"),
|
||||
)
|
||||
|
||||
with mock.patch("click.echo") as mocked_echo:
|
||||
assert cli_deploy._resolve_project(None) == "gcp-proj"
|
||||
mocked_echo.assert_called_once()
|
||||
with mock.patch("click.echo") as mocked_echo:
|
||||
assert cli_deploy._resolve_project(None) == "gcp-proj"
|
||||
mocked_echo.assert_called_once()
|
||||
|
||||
|
||||
# to_cloud_run
|
||||
@ -90,81 +94,83 @@ def test_to_cloud_run_happy_path(
|
||||
agent_dir: Callable[[bool], Path],
|
||||
include_requirements: bool,
|
||||
) -> None:
|
||||
"""
|
||||
End-to-end execution test for `to_cloud_run` covering both presence and
|
||||
absence of *requirements.txt*.
|
||||
"""
|
||||
tmp_dir = Path(tempfile.mkdtemp())
|
||||
src_dir = agent_dir(include_requirements)
|
||||
"""
|
||||
End-to-end execution test for `to_cloud_run` covering both presence and
|
||||
absence of *requirements.txt*.
|
||||
"""
|
||||
tmp_dir = Path(tempfile.mkdtemp())
|
||||
src_dir = agent_dir(include_requirements)
|
||||
|
||||
copy_recorder = _Recorder()
|
||||
run_recorder = _Recorder()
|
||||
copy_recorder = _Recorder()
|
||||
run_recorder = _Recorder()
|
||||
|
||||
# Cache the ORIGINAL copytree before patching
|
||||
original_copytree = cli_deploy.shutil.copytree
|
||||
# Cache the ORIGINAL copytree before patching
|
||||
original_copytree = cli_deploy.shutil.copytree
|
||||
|
||||
def _recording_copytree(*args: Any, **kwargs: Any):
|
||||
copy_recorder(*args, **kwargs)
|
||||
return original_copytree(*args, **kwargs)
|
||||
def _recording_copytree(*args: Any, **kwargs: Any):
|
||||
copy_recorder(*args, **kwargs)
|
||||
return original_copytree(*args, **kwargs)
|
||||
|
||||
monkeypatch.setattr(cli_deploy.shutil, "copytree", _recording_copytree)
|
||||
# Skip actual cleanup so that we can inspect generated files later.
|
||||
monkeypatch.setattr(cli_deploy.shutil, "rmtree", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr(subprocess, "run", run_recorder)
|
||||
monkeypatch.setattr(cli_deploy.shutil, "copytree", _recording_copytree)
|
||||
# Skip actual cleanup so that we can inspect generated files later.
|
||||
monkeypatch.setattr(cli_deploy.shutil, "rmtree", lambda *_a, **_k: None)
|
||||
monkeypatch.setattr(subprocess, "run", run_recorder)
|
||||
|
||||
cli_deploy.to_cloud_run(
|
||||
agent_folder=str(src_dir),
|
||||
project="proj",
|
||||
region="asia-northeast1",
|
||||
service_name="svc",
|
||||
app_name="app",
|
||||
temp_folder=str(tmp_dir),
|
||||
port=8080,
|
||||
trace_to_cloud=True,
|
||||
with_ui=True,
|
||||
verbosity="info",
|
||||
session_db_url="sqlite://",
|
||||
adk_version="0.0.5",
|
||||
)
|
||||
cli_deploy.to_cloud_run(
|
||||
agent_folder=str(src_dir),
|
||||
project="proj",
|
||||
region="asia-northeast1",
|
||||
service_name="svc",
|
||||
app_name="app",
|
||||
temp_folder=str(tmp_dir),
|
||||
port=8080,
|
||||
trace_to_cloud=True,
|
||||
with_ui=True,
|
||||
verbosity="info",
|
||||
session_db_url="sqlite://",
|
||||
adk_version="0.0.5",
|
||||
)
|
||||
|
||||
# Assertions
|
||||
assert len(copy_recorder.calls) == 1, "Agent sources must be copied exactly once."
|
||||
assert run_recorder.calls, "gcloud command should be executed at least once."
|
||||
assert (tmp_dir / "Dockerfile").exists(), "Dockerfile must be generated."
|
||||
# Assertions
|
||||
assert (
|
||||
len(copy_recorder.calls) == 1
|
||||
), "Agent sources must be copied exactly once."
|
||||
assert run_recorder.calls, "gcloud command should be executed at least once."
|
||||
assert (tmp_dir / "Dockerfile").exists(), "Dockerfile must be generated."
|
||||
|
||||
# Manual cleanup because we disabled rmtree in the monkeypatch.
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
# Manual cleanup because we disabled rmtree in the monkeypatch.
|
||||
shutil.rmtree(tmp_dir, ignore_errors=True)
|
||||
|
||||
|
||||
def test_to_cloud_run_cleans_temp_dir(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
agent_dir: Callable[[bool], Path],
|
||||
) -> None:
|
||||
"""`to_cloud_run` should always delete the temporary folder on exit."""
|
||||
tmp_dir = Path(tempfile.mkdtemp())
|
||||
src_dir = agent_dir(False)
|
||||
"""`to_cloud_run` should always delete the temporary folder on exit."""
|
||||
tmp_dir = Path(tempfile.mkdtemp())
|
||||
src_dir = agent_dir(False)
|
||||
|
||||
deleted: Dict[str, Path] = {}
|
||||
deleted: Dict[str, Path] = {}
|
||||
|
||||
def _fake_rmtree(path: str | Path, *a: Any, **k: Any) -> None:
|
||||
deleted["path"] = Path(path)
|
||||
def _fake_rmtree(path: str | Path, *a: Any, **k: Any) -> None:
|
||||
deleted["path"] = Path(path)
|
||||
|
||||
monkeypatch.setattr(cli_deploy.shutil, "rmtree", _fake_rmtree)
|
||||
monkeypatch.setattr(subprocess, "run", _Recorder())
|
||||
monkeypatch.setattr(cli_deploy.shutil, "rmtree", _fake_rmtree)
|
||||
monkeypatch.setattr(subprocess, "run", _Recorder())
|
||||
|
||||
cli_deploy.to_cloud_run(
|
||||
agent_folder=str(src_dir),
|
||||
project="proj",
|
||||
region=None,
|
||||
service_name="svc",
|
||||
app_name="app",
|
||||
temp_folder=str(tmp_dir),
|
||||
port=8080,
|
||||
trace_to_cloud=False,
|
||||
with_ui=False,
|
||||
verbosity="info",
|
||||
session_db_url=None,
|
||||
adk_version="0.0.5",
|
||||
)
|
||||
cli_deploy.to_cloud_run(
|
||||
agent_folder=str(src_dir),
|
||||
project="proj",
|
||||
region=None,
|
||||
service_name="svc",
|
||||
app_name="app",
|
||||
temp_folder=str(tmp_dir),
|
||||
port=8080,
|
||||
trace_to_cloud=False,
|
||||
with_ui=False,
|
||||
verbosity="info",
|
||||
session_db_url=None,
|
||||
adk_version="0.0.5",
|
||||
)
|
||||
|
||||
assert deleted["path"] == tmp_dir
|
||||
assert deleted["path"] == tmp_dir
|
||||
|
@ -10,4 +10,4 @@
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
# limitations under the License.
|
||||
|
@ -137,11 +137,15 @@ def test_get_input_files_not_exists(empty_state: State):
|
||||
def test_add_input_files_new(empty_state: State):
|
||||
"""Test adding input files to an empty session state."""
|
||||
ctx = CodeExecutorContext(empty_state)
|
||||
new_files = [File(name="new.dat", content="Yg==", mime_type="application/octet-stream")]
|
||||
ctx.add_input_files(new_files)
|
||||
assert empty_state["_code_executor_input_files"] == [
|
||||
{"name": "new.dat", "content": "Yg==", "mime_type": "application/octet-stream"}
|
||||
new_files = [
|
||||
File(name="new.dat", content="Yg==", mime_type="application/octet-stream")
|
||||
]
|
||||
ctx.add_input_files(new_files)
|
||||
assert empty_state["_code_executor_input_files"] == [{
|
||||
"name": "new.dat",
|
||||
"content": "Yg==",
|
||||
"mime_type": "application/octet-stream",
|
||||
}]
|
||||
|
||||
|
||||
def test_add_input_files_append(context_with_data: CodeExecutorContext):
|
||||
@ -239,9 +243,7 @@ def test_reset_error_count_no_error_key(empty_state: State):
|
||||
def test_update_code_execution_result_new_invocation(empty_state: State):
|
||||
"""Test updating code execution result for a new invocation."""
|
||||
ctx = CodeExecutorContext(empty_state)
|
||||
ctx.update_code_execution_result(
|
||||
"inv1", "print('hi')", "hi", ""
|
||||
)
|
||||
ctx.update_code_execution_result("inv1", "print('hi')", "hi", "")
|
||||
results = empty_state["_code_execution_results"]["inv1"]
|
||||
assert len(results) == 1
|
||||
assert results[0]["code"] == "print('hi')"
|
||||
@ -272,4 +274,4 @@ def test_update_code_execution_result_append(
|
||||
assert len(results) == 2
|
||||
assert results[1]["code"] == "new_code"
|
||||
assert results[1]["result_stdout"] == "new_out"
|
||||
assert results[1]["result_stderr"] == "new_err"
|
||||
assert results[1]["result_stderr"] == "new_err"
|
||||
|
@ -15,6 +15,7 @@
|
||||
"""Testings for the Trajectory Evaluator."""
|
||||
|
||||
import math
|
||||
|
||||
from google.adk.evaluation.trajectory_evaluator import TrajectoryEvaluator
|
||||
import pytest
|
||||
|
||||
|
@ -18,7 +18,8 @@ import os
|
||||
import sys
|
||||
import time
|
||||
import types as ptypes
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
from google.adk.agents.base_agent import BaseAgent
|
||||
@ -31,7 +32,6 @@ from google.adk.sessions.base_session_service import ListSessionsResponse
|
||||
from google.genai import types
|
||||
import pytest
|
||||
|
||||
|
||||
# Configure logging to help diagnose server startup issues
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
|
@ -100,6 +100,7 @@ async def test_function_system_instruction():
|
||||
" test_id."
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_async_function_system_instruction():
|
||||
async def build_function_instruction(
|
||||
|
@ -15,6 +15,7 @@
|
||||
|
||||
from unittest.mock import AsyncMock
|
||||
from unittest.mock import Mock
|
||||
|
||||
from google.adk.models.lite_llm import _content_to_message_param
|
||||
from google.adk.models.lite_llm import _function_declaration_to_tool_param
|
||||
from google.adk.models.lite_llm import _get_content
|
||||
@ -169,6 +170,7 @@ STREAMING_MODEL_RESPONSE = [
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_response():
|
||||
return ModelResponse(
|
||||
@ -264,57 +266,59 @@ async def test_generate_content_async(mock_acompletion, lite_llm_instance):
|
||||
|
||||
|
||||
litellm_append_user_content_test_cases = [
|
||||
pytest.param(
|
||||
LlmRequest(
|
||||
contents=[
|
||||
types.Content(
|
||||
role="developer",
|
||||
parts=[types.Part.from_text(text="Test prompt")]
|
||||
)
|
||||
]
|
||||
),
|
||||
2,
|
||||
id="litellm request without user content"
|
||||
),
|
||||
pytest.param(
|
||||
LlmRequest(
|
||||
contents=[
|
||||
types.Content(
|
||||
role="user",
|
||||
parts=[types.Part.from_text(text="user prompt")]
|
||||
)
|
||||
]
|
||||
),
|
||||
1,
|
||||
id="litellm request with user content"
|
||||
),
|
||||
pytest.param(
|
||||
LlmRequest(
|
||||
contents=[
|
||||
types.Content(
|
||||
role="model",
|
||||
parts=[types.Part.from_text(text="model prompt")]
|
||||
pytest.param(
|
||||
LlmRequest(
|
||||
contents=[
|
||||
types.Content(
|
||||
role="developer",
|
||||
parts=[types.Part.from_text(text="Test prompt")],
|
||||
)
|
||||
]
|
||||
),
|
||||
types.Content(
|
||||
role="user",
|
||||
parts=[types.Part.from_text(text="user prompt")]
|
||||
),
|
||||
types.Content(
|
||||
role="model",
|
||||
parts=[types.Part.from_text(text="model prompt")]
|
||||
)
|
||||
]
|
||||
2,
|
||||
id="litellm request without user content",
|
||||
),
|
||||
pytest.param(
|
||||
LlmRequest(
|
||||
contents=[
|
||||
types.Content(
|
||||
role="user",
|
||||
parts=[types.Part.from_text(text="user prompt")],
|
||||
)
|
||||
]
|
||||
),
|
||||
1,
|
||||
id="litellm request with user content",
|
||||
),
|
||||
pytest.param(
|
||||
LlmRequest(
|
||||
contents=[
|
||||
types.Content(
|
||||
role="model",
|
||||
parts=[types.Part.from_text(text="model prompt")],
|
||||
),
|
||||
types.Content(
|
||||
role="user",
|
||||
parts=[types.Part.from_text(text="user prompt")],
|
||||
),
|
||||
types.Content(
|
||||
role="model",
|
||||
parts=[types.Part.from_text(text="model prompt")],
|
||||
),
|
||||
]
|
||||
),
|
||||
4,
|
||||
id="user content is not the last message scenario",
|
||||
),
|
||||
4,
|
||||
id="user content is not the last message scenario"
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"llm_request, expected_output",
|
||||
litellm_append_user_content_test_cases
|
||||
"llm_request, expected_output", litellm_append_user_content_test_cases
|
||||
)
|
||||
def test_maybe_append_user_content(lite_llm_instance, llm_request, expected_output):
|
||||
def test_maybe_append_user_content(
|
||||
lite_llm_instance, llm_request, expected_output
|
||||
):
|
||||
|
||||
lite_llm_instance._maybe_append_user_content(llm_request)
|
||||
|
||||
|
@ -13,7 +13,6 @@
|
||||
# limitations under the License.
|
||||
|
||||
import enum
|
||||
import pytest
|
||||
|
||||
from google.adk.events import Event
|
||||
from google.adk.events import EventActions
|
||||
@ -21,6 +20,7 @@ from google.adk.sessions import DatabaseSessionService
|
||||
from google.adk.sessions import InMemorySessionService
|
||||
from google.adk.sessions.base_session_service import GetSessionConfig
|
||||
from google.genai import types
|
||||
import pytest
|
||||
|
||||
|
||||
class SessionServiceType(enum.Enum):
|
||||
|
@ -24,7 +24,6 @@ from google.adk.sessions import VertexAiSessionService
|
||||
from google.genai import types
|
||||
import pytest
|
||||
|
||||
|
||||
MOCK_SESSION_JSON_1 = {
|
||||
'name': (
|
||||
'projects/test-project/locations/test-location/'
|
||||
|
@ -14,7 +14,9 @@
|
||||
|
||||
import base64
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from google.adk.tools.apihub_tool.clients.apihub_client import APIHubClient
|
||||
import pytest
|
||||
from requests.exceptions import HTTPError
|
||||
@ -464,9 +466,7 @@ class TestAPIHubClient:
|
||||
MagicMock(
|
||||
status_code=200,
|
||||
json=lambda: {
|
||||
"name": (
|
||||
"projects/test-project/locations/us-central1/apis/api1/versions/v1"
|
||||
),
|
||||
"name": "projects/test-project/locations/us-central1/apis/api1/versions/v1",
|
||||
"specs": [],
|
||||
},
|
||||
), # No specs
|
||||
|
@ -16,7 +16,8 @@ from typing import Any
|
||||
from typing import Dict
|
||||
from typing import List
|
||||
|
||||
from fastapi.openapi.models import Response, Schema
|
||||
from fastapi.openapi.models import Response
|
||||
from fastapi.openapi.models import Schema
|
||||
from google.adk.tools.openapi_tool.common.common import ApiParameter
|
||||
from google.adk.tools.openapi_tool.common.common import PydocHelper
|
||||
from google.adk.tools.openapi_tool.common.common import rename_python_keywords
|
||||
|
@ -371,9 +371,7 @@ def test_parse_external_ref_raises_error(openapi_spec_generator):
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": (
|
||||
"external_file.json#/components/schemas/ExternalSchema"
|
||||
)
|
||||
"$ref": "external_file.json#/components/schemas/ExternalSchema"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -14,9 +14,11 @@
|
||||
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock, patch
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import patch
|
||||
|
||||
from fastapi.openapi.models import MediaType, Operation
|
||||
from fastapi.openapi.models import MediaType
|
||||
from fastapi.openapi.models import Operation
|
||||
from fastapi.openapi.models import Parameter as OpenAPIParameter
|
||||
from fastapi.openapi.models import RequestBody
|
||||
from fastapi.openapi.models import Schema as OpenAPISchema
|
||||
@ -25,13 +27,13 @@ from google.adk.tools.openapi_tool.auth.auth_helpers import token_to_scheme_cred
|
||||
from google.adk.tools.openapi_tool.common.common import ApiParameter
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.openapi_spec_parser import OperationEndpoint
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.operation_parser import OperationParser
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import (
|
||||
RestApiTool,
|
||||
snake_to_lower_camel,
|
||||
to_gemini_schema,
|
||||
)
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import RestApiTool
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import snake_to_lower_camel
|
||||
from google.adk.tools.openapi_tool.openapi_spec_parser.rest_api_tool import to_gemini_schema
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
from google.genai.types import FunctionDeclaration, Schema, Type
|
||||
from google.genai.types import FunctionDeclaration
|
||||
from google.genai.types import Schema
|
||||
from google.genai.types import Type
|
||||
import pytest
|
||||
|
||||
|
||||
|
@ -161,11 +161,9 @@ async def test_run_async_1_missing_arg_sync_func():
|
||||
args = {"arg1": "test_value_1"}
|
||||
result = await tool.run_async(args=args, tool_context=MagicMock())
|
||||
assert result == {
|
||||
"error": (
|
||||
"""Invoking `function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
"error": """Invoking `function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
arg2
|
||||
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -176,11 +174,9 @@ async def test_run_async_1_missing_arg_async_func():
|
||||
args = {"arg2": "test_value_1"}
|
||||
result = await tool.run_async(args=args, tool_context=MagicMock())
|
||||
assert result == {
|
||||
"error": (
|
||||
"""Invoking `async_function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
"error": """Invoking `async_function_for_testing_with_2_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
arg1
|
||||
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -191,13 +187,11 @@ async def test_run_async_3_missing_arg_sync_func():
|
||||
args = {"arg2": "test_value_1"}
|
||||
result = await tool.run_async(args=args, tool_context=MagicMock())
|
||||
assert result == {
|
||||
"error": (
|
||||
"""Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
"error": """Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
arg1
|
||||
arg3
|
||||
arg4
|
||||
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -208,13 +202,11 @@ async def test_run_async_3_missing_arg_async_func():
|
||||
args = {"arg3": "test_value_1"}
|
||||
result = await tool.run_async(args=args, tool_context=MagicMock())
|
||||
assert result == {
|
||||
"error": (
|
||||
"""Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
"error": """Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
arg1
|
||||
arg2
|
||||
arg4
|
||||
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -225,14 +217,12 @@ async def test_run_async_missing_all_arg_sync_func():
|
||||
args = {}
|
||||
result = await tool.run_async(args=args, tool_context=MagicMock())
|
||||
assert result == {
|
||||
"error": (
|
||||
"""Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
"error": """Invoking `function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
arg1
|
||||
arg2
|
||||
arg3
|
||||
arg4
|
||||
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -243,14 +233,12 @@ async def test_run_async_missing_all_arg_async_func():
|
||||
args = {}
|
||||
result = await tool.run_async(args=args, tool_context=MagicMock())
|
||||
assert result == {
|
||||
"error": (
|
||||
"""Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
"error": """Invoking `async_function_for_testing_with_4_arg_and_no_tool_context()` failed as the following mandatory input parameters are not present:
|
||||
arg1
|
||||
arg2
|
||||
arg3
|
||||
arg4
|
||||
You could retry calling this tool, but it is IMPORTANT for you to provide all the mandatory parameters."""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user