chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
# Copyright 2026 Google LLC
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# 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.
|
||||
|
||||
"""Tests for CacheMetadata."""
|
||||
|
||||
import time
|
||||
|
||||
from google.adk.models.cache_metadata import CacheMetadata
|
||||
from pydantic import ValidationError
|
||||
import pytest
|
||||
|
||||
|
||||
class TestCacheMetadata:
|
||||
"""Test suite for CacheMetadata."""
|
||||
|
||||
def test_required_fields(self):
|
||||
"""Test that all required fields must be provided."""
|
||||
# Valid creation with all required fields
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=5,
|
||||
contents_count=3,
|
||||
)
|
||||
|
||||
assert (
|
||||
metadata.cache_name
|
||||
== "projects/123/locations/us-central1/cachedContents/456"
|
||||
)
|
||||
assert metadata.expire_time > time.time()
|
||||
assert metadata.fingerprint == "abc123"
|
||||
assert metadata.invocations_used == 5
|
||||
assert metadata.contents_count == 3
|
||||
assert metadata.created_at is None # Optional field
|
||||
|
||||
def test_optional_created_at(self):
|
||||
"""Test that created_at is optional."""
|
||||
current_time = time.time()
|
||||
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=3,
|
||||
contents_count=2,
|
||||
created_at=current_time,
|
||||
)
|
||||
|
||||
assert metadata.created_at == current_time
|
||||
|
||||
def test_invocations_used_validation(self):
|
||||
"""Test invocations_used validation constraints."""
|
||||
# Valid: zero or positive
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=0,
|
||||
contents_count=1,
|
||||
)
|
||||
assert metadata.invocations_used == 0
|
||||
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=10,
|
||||
contents_count=1,
|
||||
)
|
||||
assert metadata.invocations_used == 10
|
||||
|
||||
# Invalid: negative
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=-1,
|
||||
contents_count=1,
|
||||
)
|
||||
assert "greater than or equal to 0" in str(exc_info.value)
|
||||
|
||||
def test_contents_count_validation(self):
|
||||
"""Test contents_count validation constraints."""
|
||||
# Valid: zero or positive
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=1,
|
||||
contents_count=0,
|
||||
)
|
||||
assert metadata.contents_count == 0
|
||||
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=1,
|
||||
contents_count=10,
|
||||
)
|
||||
assert metadata.contents_count == 10
|
||||
|
||||
# Invalid: negative
|
||||
with pytest.raises(ValidationError) as exc_info:
|
||||
CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=1,
|
||||
contents_count=-1,
|
||||
)
|
||||
assert "greater than or equal to 0" in str(exc_info.value)
|
||||
|
||||
def test_expire_soon_property(self):
|
||||
"""Test expire_soon property."""
|
||||
# Cache that expires in 10 minutes (should not expire soon)
|
||||
future_time = time.time() + 600 # 10 minutes
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=future_time,
|
||||
fingerprint="abc123",
|
||||
invocations_used=1,
|
||||
contents_count=1,
|
||||
)
|
||||
assert not metadata.expire_soon
|
||||
|
||||
# Cache that expires in 1 minute (should expire soon)
|
||||
soon_time = time.time() + 60 # 1 minute
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=soon_time,
|
||||
fingerprint="abc123",
|
||||
invocations_used=1,
|
||||
contents_count=1,
|
||||
)
|
||||
assert metadata.expire_soon
|
||||
|
||||
def test_str_representation(self):
|
||||
"""Test string representation."""
|
||||
current_time = time.time()
|
||||
expire_time = current_time + 1800 # 30 minutes
|
||||
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/test456",
|
||||
expire_time=expire_time,
|
||||
fingerprint="abc123",
|
||||
invocations_used=7,
|
||||
contents_count=4,
|
||||
)
|
||||
|
||||
str_repr = str(metadata)
|
||||
assert "test456" in str_repr # Cache ID
|
||||
assert "used 7 invocations" in str_repr
|
||||
assert "cached 4 contents" in str_repr
|
||||
assert "expires in" in str_repr
|
||||
|
||||
def test_immutability(self):
|
||||
"""Test that CacheMetadata is immutable (frozen)."""
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=5,
|
||||
contents_count=3,
|
||||
)
|
||||
|
||||
# Should not be able to modify fields
|
||||
with pytest.raises(ValidationError):
|
||||
metadata.invocations_used = 10
|
||||
|
||||
def test_model_config(self):
|
||||
"""Test that model config is set correctly."""
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=5,
|
||||
contents_count=3,
|
||||
)
|
||||
|
||||
assert metadata.model_config["extra"] == "forbid"
|
||||
assert metadata.model_config["frozen"] == True
|
||||
|
||||
def test_field_descriptions(self):
|
||||
"""Test that fields have proper descriptions."""
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=5,
|
||||
contents_count=3,
|
||||
)
|
||||
schema = metadata.model_json_schema()
|
||||
|
||||
assert "invocations_used" in schema["properties"]
|
||||
assert (
|
||||
"Number of invocations"
|
||||
in schema["properties"]["invocations_used"]["description"]
|
||||
)
|
||||
|
||||
assert "contents_count" in schema["properties"]
|
||||
assert (
|
||||
"Number of contents"
|
||||
in schema["properties"]["contents_count"]["description"]
|
||||
)
|
||||
|
||||
def test_realistic_cache_scenarios(self):
|
||||
"""Test realistic cache scenarios."""
|
||||
current_time = time.time()
|
||||
|
||||
# Fresh cache
|
||||
fresh_cache = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/fresh123",
|
||||
expire_time=current_time + 1800,
|
||||
fingerprint="fresh_fingerprint",
|
||||
invocations_used=1,
|
||||
contents_count=5,
|
||||
created_at=current_time,
|
||||
)
|
||||
assert fresh_cache.invocations_used == 1
|
||||
assert not fresh_cache.expire_soon
|
||||
|
||||
# Well-used cache
|
||||
used_cache = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/used456",
|
||||
expire_time=current_time + 600,
|
||||
fingerprint="used_fingerprint",
|
||||
invocations_used=8,
|
||||
contents_count=3,
|
||||
created_at=current_time - 1200,
|
||||
)
|
||||
assert used_cache.invocations_used == 8
|
||||
|
||||
# Expiring cache
|
||||
expiring_cache = CacheMetadata(
|
||||
cache_name=(
|
||||
"projects/123/locations/us-central1/cachedContents/expiring789"
|
||||
),
|
||||
expire_time=current_time + 60, # 1 minute
|
||||
fingerprint="expiring_fingerprint",
|
||||
invocations_used=15,
|
||||
contents_count=10,
|
||||
)
|
||||
assert expiring_cache.expire_soon
|
||||
|
||||
def test_cache_name_extraction(self):
|
||||
"""Test cache name ID extraction in string representation."""
|
||||
metadata = CacheMetadata(
|
||||
cache_name=(
|
||||
"projects/123/locations/us-central1/cachedContents/extracted_id"
|
||||
),
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=1,
|
||||
contents_count=2,
|
||||
)
|
||||
|
||||
str_repr = str(metadata)
|
||||
assert "extracted_id" in str_repr
|
||||
|
||||
def test_no_performance_metrics(self):
|
||||
"""Test that performance metrics are not in CacheMetadata."""
|
||||
metadata = CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/456",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc123",
|
||||
invocations_used=5,
|
||||
contents_count=3,
|
||||
)
|
||||
|
||||
# Verify that token counts are NOT in CacheMetadata
|
||||
# (they should be in LlmResponse.usage_metadata)
|
||||
assert not hasattr(metadata, "cached_tokens")
|
||||
assert not hasattr(metadata, "total_tokens")
|
||||
assert not hasattr(metadata, "prompt_tokens")
|
||||
|
||||
def test_missing_required_fields(self):
|
||||
"""Test validation when truly required fields are missing."""
|
||||
# Only fingerprint and contents_count are required now
|
||||
# Other fields are optional (for fingerprint-only state)
|
||||
required_fields = [
|
||||
"fingerprint",
|
||||
"contents_count",
|
||||
]
|
||||
|
||||
base_args = {
|
||||
"fingerprint": "abc123",
|
||||
"contents_count": 2,
|
||||
}
|
||||
|
||||
for field in required_fields:
|
||||
args = base_args.copy()
|
||||
del args[field]
|
||||
|
||||
with pytest.raises(ValidationError):
|
||||
CacheMetadata(**args)
|
||||
|
||||
# Test that optional fields can be omitted (fingerprint-only state)
|
||||
metadata = CacheMetadata(
|
||||
fingerprint="abc123",
|
||||
contents_count=5,
|
||||
)
|
||||
assert metadata.cache_name is None
|
||||
assert metadata.expire_time is None
|
||||
assert metadata.invocations_used is None
|
||||
assert metadata.created_at is None
|
||||
|
||||
def test_partial_active_state_rejected(self):
|
||||
"""cache_name, expire_time, invocations_used must all be set or all None."""
|
||||
# Only cache_name set.
|
||||
with pytest.raises(ValidationError, match="must all be set"):
|
||||
CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/x",
|
||||
fingerprint="abc",
|
||||
contents_count=1,
|
||||
)
|
||||
|
||||
# cache_name + expire_time but no invocations_used.
|
||||
with pytest.raises(ValidationError, match="must all be set"):
|
||||
CacheMetadata(
|
||||
cache_name="projects/123/locations/us-central1/cachedContents/x",
|
||||
expire_time=time.time() + 1800,
|
||||
fingerprint="abc",
|
||||
contents_count=1,
|
||||
)
|
||||
|
||||
# invocations_used set without cache_name (e.g. construction bug).
|
||||
with pytest.raises(ValidationError, match="must all be set"):
|
||||
CacheMetadata(
|
||||
fingerprint="abc",
|
||||
invocations_used=3,
|
||||
contents_count=1,
|
||||
)
|
||||
Reference in New Issue
Block a user