Skip to content

Commit a08ae2c

Browse files
caohy1988claude
andcommitted
test: add BQAA tests for AGENT_ERROR and INVOCATION_ERROR events
Closes the coverage gap for the error callback BQAA integration: - test_on_agent_error_callback_logs_correctly: AGENT_ERROR event with error_message, status=ERROR, and traceback in content - test_on_run_error_callback_logs_correctly: INVOCATION_ERROR event with same fields - test_on_run_error_callback_cleanup_runs_on_log_failure: TraceManager cleanup and context var reset run even when _log_event raises - test_traceback_not_truncated_with_negative_max_len: max_content_length=-1 does not truncate (validates the max_len > 0 fix) - test_error_views_contain_traceback_column: view SQL includes error_traceback for both AGENT_ERROR and INVOCATION_ERROR views Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cf62fdd commit a08ae2c

1 file changed

Lines changed: 159 additions & 0 deletions

File tree

tests/unittests/plugins/test_bigquery_agent_analytics_plugin.py

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1807,6 +1807,144 @@ async def test_on_tool_error_callback_logs_correctly(
18071807
assert log_entry["error_message"] == "Tool timed out"
18081808
assert log_entry["status"] == "ERROR"
18091809

1810+
@pytest.mark.asyncio
1811+
async def test_on_agent_error_callback_logs_correctly(
1812+
self,
1813+
bq_plugin_inst,
1814+
mock_write_client,
1815+
callback_context,
1816+
mock_agent,
1817+
dummy_arrow_schema,
1818+
):
1819+
"""on_agent_error_callback emits AGENT_ERROR with traceback."""
1820+
error = RuntimeError("Agent crashed")
1821+
try:
1822+
raise error
1823+
except RuntimeError:
1824+
pass # populate __traceback__
1825+
bigquery_agent_analytics_plugin.TraceManager.push_span(callback_context)
1826+
await bq_plugin_inst.on_agent_error_callback(
1827+
agent=mock_agent,
1828+
callback_context=callback_context,
1829+
error=error,
1830+
)
1831+
await asyncio.sleep(0.05)
1832+
rows = await _get_captured_rows_async(mock_write_client, dummy_arrow_schema)
1833+
log_entry = next(r for r in rows if r["event_type"] == "AGENT_ERROR")
1834+
assert log_entry["error_message"] == "Agent crashed"
1835+
assert log_entry["status"] == "ERROR"
1836+
content = json.loads(log_entry["content"])
1837+
assert "error_traceback" in content
1838+
assert "RuntimeError: Agent crashed" in content["error_traceback"]
1839+
1840+
@pytest.mark.asyncio
1841+
async def test_on_run_error_callback_logs_correctly(
1842+
self,
1843+
bq_plugin_inst,
1844+
mock_write_client,
1845+
invocation_context,
1846+
dummy_arrow_schema,
1847+
):
1848+
"""on_run_error_callback emits INVOCATION_ERROR with traceback."""
1849+
error = ValueError("Invocation failed")
1850+
try:
1851+
raise error
1852+
except ValueError:
1853+
pass
1854+
bigquery_agent_analytics_plugin.TraceManager.push_span(invocation_context)
1855+
await bq_plugin_inst.on_run_error_callback(
1856+
invocation_context=invocation_context,
1857+
error=error,
1858+
)
1859+
await asyncio.sleep(0.05)
1860+
rows = await _get_captured_rows_async(mock_write_client, dummy_arrow_schema)
1861+
log_entry = next(r for r in rows if r["event_type"] == "INVOCATION_ERROR")
1862+
assert log_entry["error_message"] == "Invocation failed"
1863+
assert log_entry["status"] == "ERROR"
1864+
content = json.loads(log_entry["content"])
1865+
assert "error_traceback" in content
1866+
assert "ValueError: Invocation failed" in content["error_traceback"]
1867+
1868+
@pytest.mark.asyncio
1869+
async def test_on_run_error_callback_cleanup_runs_on_log_failure(
1870+
self,
1871+
bq_plugin_inst,
1872+
mock_write_client,
1873+
invocation_context,
1874+
):
1875+
"""on_run_error_callback cleans up even when _log_event raises."""
1876+
# Push spans and set context vars to simulate active invocation
1877+
bigquery_agent_analytics_plugin.TraceManager.push_span(invocation_context)
1878+
bigquery_agent_analytics_plugin._active_invocation_id_ctx.set("test-inv")
1879+
bigquery_agent_analytics_plugin._root_agent_name_ctx.set("test-agent")
1880+
1881+
# Make _log_event raise
1882+
with mock.patch.object(
1883+
bq_plugin_inst, "_log_event", side_effect=RuntimeError("boom")
1884+
):
1885+
# @_safe_callback swallows the exception
1886+
await bq_plugin_inst.on_run_error_callback(
1887+
invocation_context=invocation_context,
1888+
error=ValueError("app error"),
1889+
)
1890+
1891+
# finally block must have cleaned up
1892+
assert (
1893+
bigquery_agent_analytics_plugin._active_invocation_id_ctx.get(None)
1894+
is None
1895+
)
1896+
assert (
1897+
bigquery_agent_analytics_plugin._root_agent_name_ctx.get(None) is None
1898+
)
1899+
1900+
@pytest.mark.asyncio
1901+
async def test_traceback_not_truncated_with_negative_max_len(
1902+
self,
1903+
mock_auth_default,
1904+
mock_bq_client,
1905+
mock_write_client,
1906+
mock_to_arrow_schema,
1907+
mock_asyncio_to_thread,
1908+
invocation_context,
1909+
mock_agent,
1910+
dummy_arrow_schema,
1911+
):
1912+
"""Traceback is not truncated when max_content_length is -1."""
1913+
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig(
1914+
max_content_length=-1,
1915+
create_views=False,
1916+
)
1917+
async with managed_plugin(
1918+
project_id=PROJECT_ID,
1919+
dataset_id=DATASET_ID,
1920+
table_id=TABLE_ID,
1921+
config=config,
1922+
) as plugin:
1923+
await plugin._ensure_started()
1924+
1925+
error = RuntimeError("x" * 2000)
1926+
try:
1927+
raise error
1928+
except RuntimeError:
1929+
pass
1930+
bigquery_agent_analytics_plugin.TraceManager.push_span(invocation_context)
1931+
await plugin.on_agent_error_callback(
1932+
agent=mock_agent,
1933+
callback_context=bigquery_agent_analytics_plugin.CallbackContext(
1934+
invocation_context
1935+
),
1936+
error=error,
1937+
)
1938+
await asyncio.sleep(0.05)
1939+
rows = await _get_captured_rows_async(
1940+
mock_write_client, dummy_arrow_schema
1941+
)
1942+
log_entry = next(r for r in rows if r["event_type"] == "AGENT_ERROR")
1943+
content = json.loads(log_entry["content"])
1944+
# Should NOT be truncated
1945+
assert "[truncated]" not in content["error_traceback"]
1946+
assert "x" * 2000 in content["error_traceback"]
1947+
18101948
@pytest.mark.asyncio
18111949
async def test_table_creation_options(
18121950
self,
@@ -5733,6 +5871,27 @@ def test_view_sql_contains_correct_event_filter(self):
57335871
view_name = "v_" + event_type.lower()
57345872
assert view_name in all_sql, f"View {view_name} not found in SQL"
57355873

5874+
def test_error_views_contain_traceback_column(self):
5875+
"""AGENT_ERROR and INVOCATION_ERROR views include error_traceback."""
5876+
plugin = self._make_plugin(create_views=True)
5877+
plugin.client.get_table.side_effect = cloud_exceptions.NotFound("not found")
5878+
mock_query_job = mock.MagicMock()
5879+
plugin.client.query.return_value = mock_query_job
5880+
5881+
plugin._ensure_schema_exists()
5882+
5883+
calls = plugin.client.query.call_args_list
5884+
all_sqls = {c[0][0] for c in calls}
5885+
5886+
agent_error_sqls = [s for s in all_sqls if "v_agent_error" in s]
5887+
assert len(agent_error_sqls) == 1
5888+
assert "error_traceback" in agent_error_sqls[0]
5889+
assert "total_ms" in agent_error_sqls[0]
5890+
5891+
inv_error_sqls = [s for s in all_sqls if "v_invocation_error" in s]
5892+
assert len(inv_error_sqls) == 1
5893+
assert "error_traceback" in inv_error_sqls[0]
5894+
57365895
def test_config_create_views_default_true(self):
57375896
"""Config create_views defaults to True."""
57385897
config = bigquery_agent_analytics_plugin.BigQueryLoggerConfig()

0 commit comments

Comments
 (0)