@@ -298,21 +298,23 @@ async def test_event_allowlist(
298298 mock_write_client .append_rows .assert_called_once ()
299299 mock_write_client .append_rows .reset_mock ()
300300
301- # Re-init plugin logic since close() shuts it down, but for this test we want to test denial
302- # However, close() cleans up clients. We should probably create a new plugin or just check that the task was not created.
303- # But on_user_message_callback will try to log.
304- # To keep it simple, let's just use a fresh plugin for the second part or assume close() resets state enough to re-run _ensure_init if needed,
305- # but _ensure_init is called inside _perform_write.
306- # Actually, close() sets _is_shutting_down to True, so further logs are ignored.
307- # So we need a new plugin instance or reset _is_shutting_down.
308- plugin ._is_shutting_down = False
301+ # REFACTOR: Use a fresh plugin instance for the denied case
302+ plugin_denied = (
303+ bigquery_agent_analytics_plugin .BigQueryAgentAnalyticsPlugin (
304+ PROJECT_ID , DATASET_ID , TABLE_ID , config
305+ )
306+ )
307+ await plugin_denied ._ensure_init ()
308+ # Inject the same mock_write_client
309+ plugin_denied ._write_client = mock_write_client
310+ plugin_denied ._arrow_schema = plugin ._arrow_schema
309311
310312 user_message = types .Content (parts = [types .Part (text = "What is up?" )])
311- await plugin .on_user_message_callback (
313+ await plugin_denied .on_user_message_callback (
312314 invocation_context = invocation_context , user_message = user_message
313315 )
314316 # Since it's denied, no task is created. close() would wait if there was one.
315- await plugin .close ()
317+ await plugin_denied .close ()
316318 mock_write_client .append_rows .assert_not_called ()
317319
318320 @pytest .mark .asyncio
@@ -340,11 +342,21 @@ async def test_event_denylist(
340342 await plugin .close ()
341343 mock_write_client .append_rows .assert_not_called ()
342344
343- # Reset for next call
344- plugin ._is_shutting_down = False
345+ # REFACTOR: Use a fresh plugin instance for the allowed case
346+ plugin_allowed = (
347+ bigquery_agent_analytics_plugin .BigQueryAgentAnalyticsPlugin (
348+ PROJECT_ID , DATASET_ID , TABLE_ID , config
349+ )
350+ )
351+ await plugin_allowed ._ensure_init ()
352+ # Inject the same mock_write_client
353+ plugin_allowed ._write_client = mock_write_client
354+ plugin_allowed ._arrow_schema = plugin ._arrow_schema
345355
346- await plugin .before_run_callback (invocation_context = invocation_context )
347- await plugin .close ()
356+ await plugin_allowed .before_run_callback (
357+ invocation_context = invocation_context
358+ )
359+ await plugin_allowed .close ()
348360 mock_write_client .append_rows .assert_called_once ()
349361
350362 @pytest .mark .asyncio
@@ -399,6 +411,44 @@ def mutate_payload(data):
399411 assert content ["model" ] == "GEMINI-PRO"
400412 assert content ["prompt" ][0 ]["role" ] == "user"
401413
414+ @pytest .mark .asyncio
415+ async def test_content_formatter_error_fallback (
416+ self ,
417+ mock_write_client ,
418+ invocation_context ,
419+ mock_auth_default ,
420+ mock_bq_client ,
421+ mock_to_arrow_schema ,
422+ dummy_arrow_schema ,
423+ mock_asyncio_to_thread ,
424+ ):
425+ """Tests that if content_formatter fails, the original payload is used."""
426+
427+ def error_formatter (data ):
428+ raise ValueError ("Formatter failed" )
429+
430+ config = BigQueryLoggerConfig (content_formatter = error_formatter )
431+ plugin = bigquery_agent_analytics_plugin .BigQueryAgentAnalyticsPlugin (
432+ PROJECT_ID , DATASET_ID , TABLE_ID , config
433+ )
434+ await plugin ._ensure_init ()
435+ mock_write_client .append_rows .reset_mock ()
436+
437+ user_message = types .Content (parts = [types .Part (text = "Original message" )])
438+
439+ # This triggers the log. Internal logic catches exception and proceeds.
440+ await plugin .on_user_message_callback (
441+ invocation_context = invocation_context , user_message = user_message
442+ )
443+ await plugin .close ()
444+
445+ mock_write_client .append_rows .assert_called_once ()
446+ log_entry = _get_captured_event_dict (mock_write_client , dummy_arrow_schema )
447+
448+ # Verify that despite the error, we still got the original data
449+ content = json .loads (log_entry ["content" ])
450+ assert content ["text" ] == "Original message"
451+
402452 @pytest .mark .asyncio
403453 async def test_max_content_length_smart_truncation (
404454 self ,
@@ -725,7 +775,9 @@ async def test_before_tool_callback_logs_correctly(
725775 type(mock_tool ).name = mock .PropertyMock (return_value = "MyTool" )
726776 type(mock_tool ).description = mock .PropertyMock (return_value = "Description" )
727777 await bq_plugin_inst .before_tool_callback (
728- tool = mock_tool , tool_args = {"param" : "value" }, tool_context = tool_context
778+ tool = mock_tool ,
779+ tool_args = {"param" : "value" },
780+ tool_context = tool_context ,
729781 )
730782 await bq_plugin_inst .close ()
731783 log_entry = _get_captured_event_dict (mock_write_client , dummy_arrow_schema )
0 commit comments