|
19 | 19 |
|
20 | 20 | from google.adk.telemetry import _instrumentation |
21 | 21 | from opentelemetry import trace |
| 22 | +import pytest |
22 | 23 |
|
23 | 24 |
|
24 | 25 | def test_get_elapsed_ms_span_none(): |
@@ -80,3 +81,37 @@ def test_get_elapsed_ms_span_non_int_end(): |
80 | 81 | with mock.patch("time.monotonic", return_value=12.0): |
81 | 82 | elapsed = _instrumentation._get_elapsed_ms(mock_span, start_time) |
82 | 83 | assert elapsed == 2000.0 |
| 84 | + |
| 85 | + |
| 86 | +@pytest.mark.asyncio |
| 87 | +async def test_record_agent_invocation_tolerates_minimal_context(): |
| 88 | + """Tolerates context-likes that lack user_content or session. |
| 89 | +
|
| 90 | + Test doubles, partial migrations, and external embedders can pass an |
| 91 | + InvocationContext-like object without `user_content` or with a `session` |
| 92 | + that has no `events` attribute. The telemetry path must not raise |
| 93 | + AttributeError on the metrics call in those cases. |
| 94 | + """ |
| 95 | + agent = mock.MagicMock() |
| 96 | + agent.name = "test_agent" |
| 97 | + # Bare object without `user_content` and without `session`. |
| 98 | + bare_ctx = object() |
| 99 | + |
| 100 | + with ( |
| 101 | + mock.patch.object( |
| 102 | + _instrumentation, "_record_agent_metrics" |
| 103 | + ) as mock_record, |
| 104 | + mock.patch.object(_instrumentation, "tracing") as mock_tracing, |
| 105 | + ): |
| 106 | + mock_tracing.tracer.start_as_current_span.return_value.__enter__.return_value = mock.MagicMock( |
| 107 | + spec=trace.Span |
| 108 | + ) |
| 109 | + async with _instrumentation.record_agent_invocation(bare_ctx, agent): |
| 110 | + pass |
| 111 | + |
| 112 | + mock_record.assert_called_once() |
| 113 | + call_args = mock_record.call_args |
| 114 | + # positional: (agent_name, elapsed_ms, user_content, events, caught_error) |
| 115 | + assert call_args.args[0] == "test_agent" |
| 116 | + assert call_args.args[2] is None # user_content default |
| 117 | + assert call_args.args[3] == [] # events default |
0 commit comments