|
21 | 21 | import zipfile |
22 | 22 |
|
23 | 23 | from google.adk.skills import list_skills_in_dir |
| 24 | +from google.adk.skills import list_skills_in_dir_async as _list_skills_in_dir_async |
24 | 25 | from google.adk.skills import list_skills_in_gcs_dir as _list_skills_in_gcs_dir |
| 26 | +from google.adk.skills import list_skills_in_gcs_dir_async as _list_skills_in_gcs_dir_async |
25 | 27 | from google.adk.skills import load_skill_from_dir as _load_skill_from_dir |
| 28 | +from google.adk.skills import load_skill_from_dir_async as _load_skill_from_dir_async |
26 | 29 | from google.adk.skills import load_skill_from_gcs_dir as _load_skill_from_gcs_dir |
| 30 | +from google.adk.skills import load_skill_from_gcs_dir_async as _load_skill_from_gcs_dir_async |
27 | 31 | from google.adk.skills._utils import _load_skill_from_zip_bytes |
28 | 32 | from google.adk.skills._utils import _read_skill_properties |
29 | 33 | from google.adk.skills._utils import _validate_skill_dir |
@@ -393,3 +397,117 @@ def mock_import(name, globals=None, locals=None, fromlist=(), level=0): |
393 | 397 | with mock.patch("builtins.__import__", mock_import): |
394 | 398 | with pytest.raises(ImportError, match="google-cloud-storage is required"): |
395 | 399 | _load_skill_from_gcs_dir("my-bucket", "skills/my-skill/") |
| 400 | + |
| 401 | + |
| 402 | +@pytest.mark.asyncio |
| 403 | +async def test_load_skill_from_dir_async(tmp_path): |
| 404 | + """Tests loading a skill from a directory asynchronously.""" |
| 405 | + skill_dir = tmp_path / "test-skill" |
| 406 | + skill_dir.mkdir() |
| 407 | + |
| 408 | + skill_md_content = """--- |
| 409 | +name: test-skill |
| 410 | +description: Test description |
| 411 | +--- |
| 412 | +Test instructions |
| 413 | +""" |
| 414 | + (skill_dir / "SKILL.md").write_text(skill_md_content) |
| 415 | + |
| 416 | + # Create references |
| 417 | + ref_dir = skill_dir / "references" |
| 418 | + ref_dir.mkdir() |
| 419 | + (ref_dir / "ref1.md").write_text("ref1 content") |
| 420 | + |
| 421 | + skill = await _load_skill_from_dir_async(skill_dir) |
| 422 | + |
| 423 | + assert skill.name == "test-skill" |
| 424 | + assert skill.description == "Test description" |
| 425 | + assert skill.instructions == "Test instructions" |
| 426 | + assert skill.resources.get_reference("ref1.md") == "ref1 content" |
| 427 | + |
| 428 | + |
| 429 | +@pytest.mark.asyncio |
| 430 | +async def test_list_skills_in_dir_async(tmp_path): |
| 431 | + """Tests listing skills in a directory asynchronously.""" |
| 432 | + skills_dir = tmp_path / "skills" |
| 433 | + skills_dir.mkdir() |
| 434 | + |
| 435 | + # Valid skill 1 |
| 436 | + skill1_dir = skills_dir / "skill1" |
| 437 | + skill1_dir.mkdir() |
| 438 | + (skill1_dir / "SKILL.md").write_text( |
| 439 | + "---\nname: skill1\ndescription: desc1\n---\nbody" |
| 440 | + ) |
| 441 | + |
| 442 | + skills = await _list_skills_in_dir_async(skills_dir) |
| 443 | + |
| 444 | + assert len(skills) == 1 |
| 445 | + assert "skill1" in skills |
| 446 | + assert skills["skill1"].name == "skill1" |
| 447 | + |
| 448 | + |
| 449 | +@pytest.mark.asyncio |
| 450 | +@mock.patch("google.cloud.storage.Client") |
| 451 | +async def test_load_skill_from_gcs_dir_async(mock_client_class): |
| 452 | + """Tests loading a skill from GCS asynchronously.""" |
| 453 | + mock_client = mock.MagicMock() |
| 454 | + mock_client_class.return_value = mock_client |
| 455 | + mock_bucket = mock.MagicMock() |
| 456 | + mock_client.bucket.return_value = mock_bucket |
| 457 | + |
| 458 | + def mock_blob_side_effect(path): |
| 459 | + m = mock.MagicMock() |
| 460 | + if path.endswith("SKILL.md"): |
| 461 | + m.exists.return_value = True |
| 462 | + m.download_as_text.return_value = ( |
| 463 | + "---\nname: my-skill\ndescription: Test description\n---\nTest" |
| 464 | + " instructions" |
| 465 | + ) |
| 466 | + else: |
| 467 | + m.exists.return_value = False |
| 468 | + return m |
| 469 | + |
| 470 | + mock_bucket.blob.side_effect = mock_blob_side_effect |
| 471 | + |
| 472 | + # For resources |
| 473 | + def list_blobs_side_effect(prefix=None): |
| 474 | + if prefix.endswith("references/"): |
| 475 | + m = mock.MagicMock() |
| 476 | + m.name = prefix + "ref1.md" |
| 477 | + m.download_as_text.return_value = "ref1 content" |
| 478 | + return [m] |
| 479 | + return [] |
| 480 | + |
| 481 | + mock_bucket.list_blobs.side_effect = list_blobs_side_effect |
| 482 | + |
| 483 | + skill = await _load_skill_from_gcs_dir_async("my-bucket", "skills/my-skill/") |
| 484 | + |
| 485 | + assert skill.name == "my-skill" |
| 486 | + assert skill.description == "Test description" |
| 487 | + assert skill.instructions == "Test instructions" |
| 488 | + assert skill.resources.get_reference("ref1.md") == "ref1 content" |
| 489 | + |
| 490 | + |
| 491 | +@pytest.mark.asyncio |
| 492 | +@mock.patch("google.cloud.storage.Client") |
| 493 | +async def test_list_skills_in_gcs_dir_async(mock_client_class): |
| 494 | + """Tests listing skills in GCS asynchronously.""" |
| 495 | + mock_client = mock.MagicMock() |
| 496 | + mock_client_class.return_value = mock_client |
| 497 | + mock_bucket = mock.MagicMock() |
| 498 | + mock_client.bucket.return_value = mock_bucket |
| 499 | + |
| 500 | + mock_iterator = mock.MagicMock() |
| 501 | + mock_iterator.prefixes = ["skills/my-skill/"] |
| 502 | + mock_bucket.list_blobs.return_value = mock_iterator |
| 503 | + |
| 504 | + mock_blob = mock.MagicMock() |
| 505 | + mock_blob.exists.return_value = True |
| 506 | + mock_blob.download_as_text.return_value = ( |
| 507 | + "---\nname: my-skill\ndescription: A skill\n---\nBody" |
| 508 | + ) |
| 509 | + mock_bucket.blob.return_value = mock_blob |
| 510 | + |
| 511 | + skills = await _list_skills_in_gcs_dir_async("my-bucket", "skills/") |
| 512 | + assert "my-skill" in skills |
| 513 | + assert skills["my-skill"].name == "my-skill" |
0 commit comments