@@ -5087,18 +5087,19 @@ def test_reset_logs_fork_warning(self):
50875087class TestAnalyticsViews :
50885088 """Tests for auto-created per-event-type BigQuery views."""
50895089
5090- def _make_plugin (self , create_views = True ):
5090+ def _make_plugin (self , create_views = True , view_prefix = "v" , table_id = TABLE_ID ):
50915091 config = bigquery_agent_analytics_plugin .BigQueryLoggerConfig (
50925092 create_views = create_views ,
5093+ view_prefix = view_prefix ,
50935094 )
50945095 plugin = bigquery_agent_analytics_plugin .BigQueryAgentAnalyticsPlugin (
50955096 project_id = PROJECT_ID ,
50965097 dataset_id = DATASET_ID ,
5097- table_id = TABLE_ID ,
5098+ table_id = table_id ,
50985099 config = config ,
50995100 )
51005101 plugin .client = mock .MagicMock ()
5101- plugin .full_table_id = f"{ PROJECT_ID } .{ DATASET_ID } .{ TABLE_ID } "
5102+ plugin .full_table_id = f"{ PROJECT_ID } .{ DATASET_ID } .{ table_id } "
51025103 plugin ._schema = bigquery_agent_analytics_plugin ._get_events_schema ()
51035104 return plugin
51045105
@@ -5184,6 +5185,7 @@ def test_config_create_views_default_true(self):
51845185 """Config create_views defaults to True."""
51855186 config = bigquery_agent_analytics_plugin .BigQueryLoggerConfig ()
51865187 assert config .create_views is True
5188+ assert config .view_prefix == "v"
51875189
51885190 @pytest .mark .asyncio
51895191 async def test_create_analytics_views_ensures_started (
@@ -5242,6 +5244,77 @@ async def test_create_analytics_views_raises_on_startup_failure(
52425244 assert exc_info .value .__cause__ is not None
52435245 assert "client boom" in str (exc_info .value .__cause__ )
52445246
5247+ def test_custom_view_prefix (self ):
5248+ """Custom view_prefix namespaces view names."""
5249+ plugin = self ._make_plugin (view_prefix = "v_staging" )
5250+ plugin .client .get_table .side_effect = cloud_exceptions .NotFound ("not found" )
5251+ mock_query_job = mock .MagicMock ()
5252+ plugin .client .query .return_value = mock_query_job
5253+
5254+ plugin ._ensure_schema_exists ()
5255+
5256+ calls = plugin .client .query .call_args_list
5257+ all_sql = " " .join (c [0 ][0 ] for c in calls )
5258+ # All views should use the custom prefix
5259+ for event_type in bigquery_agent_analytics_plugin ._EVENT_VIEW_DEFS :
5260+ expected_name = "v_staging_" + event_type .lower ()
5261+ assert expected_name in all_sql , f"View { expected_name } not found in SQL"
5262+ # Default prefix should NOT appear
5263+ assert ".v_llm_request" not in all_sql
5264+
5265+ def test_default_view_prefix_preserves_names (self ):
5266+ """Default view_prefix='v' produces the same names as before."""
5267+ plugin = self ._make_plugin () # default view_prefix="v"
5268+ plugin .client .get_table .side_effect = cloud_exceptions .NotFound ("not found" )
5269+ mock_query_job = mock .MagicMock ()
5270+ plugin .client .query .return_value = mock_query_job
5271+
5272+ plugin ._ensure_schema_exists ()
5273+
5274+ calls = plugin .client .query .call_args_list
5275+ all_sql = " " .join (c [0 ][0 ] for c in calls )
5276+ for event_type in bigquery_agent_analytics_plugin ._EVENT_VIEW_DEFS :
5277+ view_name = "v_" + event_type .lower ()
5278+ assert view_name in all_sql
5279+
5280+ def test_distinct_tables_and_prefixes_no_collision (self ):
5281+ """Two plugins targeting different tables produce disjoint views."""
5282+ plugin_a = self ._make_plugin (
5283+ table_id = "agent_events_prod" , view_prefix = "v_prod"
5284+ )
5285+ plugin_b = self ._make_plugin (
5286+ table_id = "agent_events_staging" , view_prefix = "v_staging"
5287+ )
5288+
5289+ for plugin in (plugin_a , plugin_b ):
5290+ plugin .client .get_table .side_effect = cloud_exceptions .NotFound (
5291+ "not found"
5292+ )
5293+ mock_query_job = mock .MagicMock ()
5294+ plugin .client .query .return_value = mock_query_job
5295+ plugin ._ensure_schema_exists ()
5296+
5297+ sql_a = " " .join (c [0 ][0 ] for c in plugin_a .client .query .call_args_list )
5298+ sql_b = " " .join (c [0 ][0 ] for c in plugin_b .client .query .call_args_list )
5299+
5300+ # View names use their own prefix
5301+ assert "v_prod_llm_request" in sql_a
5302+ assert "v_staging_llm_request" in sql_b
5303+ # No cross-contamination
5304+ assert "v_staging_" not in sql_a
5305+ assert "v_prod_" not in sql_b
5306+
5307+ # FROM clauses point at the correct table
5308+ assert "agent_events_prod" in sql_a
5309+ assert "agent_events_staging" not in sql_a
5310+ assert "agent_events_staging" in sql_b
5311+ assert "agent_events_prod" not in sql_b
5312+
5313+ def test_empty_view_prefix_raises (self ):
5314+ """Empty view_prefix is rejected at init."""
5315+ with pytest .raises (ValueError , match = "view_prefix" ):
5316+ self ._make_plugin (view_prefix = "" )
5317+
52455318
52465319# ==============================================================================
52475320# Trace-ID Continuity Tests (Issue #4645)
0 commit comments