@@ -700,8 +700,8 @@ async def test_update_or_create_tools_existing_tools(self):
700700 tools = [mock_tool ]
701701 context = "update"
702702
703- # Call the helper method
704- result = service ._update_or_create_tools (mock_db , tools , mock_gateway , context )
703+ # Call the helper method (with explicit visibility change)
704+ result = service ._update_or_create_tools (mock_db , tools , mock_gateway , context , update_visibility = True )
705705
706706 # Should return empty list (no new tools, existing one updated)
707707 assert len (result ) == 0
@@ -851,8 +851,8 @@ async def test_update_or_create_resources_existing_resources(self):
851851 resources = [mock_resource ]
852852 context = "update"
853853
854- # Call method
855- result = service ._update_or_create_resources (mock_db , resources , mock_gateway , context )
854+ # Call method (with explicit visibility change)
855+ result = service ._update_or_create_resources (mock_db , resources , mock_gateway , context , update_visibility = True )
856856
857857 # Should return empty list (no new resources)
858858 assert len (result ) == 0
@@ -943,8 +943,8 @@ async def test_update_or_create_prompts_existing_prompts(self):
943943 prompts = [mock_prompt ]
944944 context = "update"
945945
946- # Call the helper method
947- result = service ._update_or_create_prompts (mock_db , prompts , mock_gateway , context )
946+ # Call the helper method (with explicit visibility change)
947+ result = service ._update_or_create_prompts (mock_db , prompts , mock_gateway , context , update_visibility = True )
948948
949949 # Should return empty list (no new prompts, existing one updated)
950950 assert len (result ) == 0
@@ -1048,7 +1048,7 @@ async def test_helper_methods_mixed_operations(self):
10481048 assert existing_tool2 .original_description == "Updated description"
10491049 assert existing_tool2 .url == "http://new.com" # Updated from gateway
10501050 assert existing_tool2 .auth_type == "bearer" # Updated from gateway
1051- assert existing_tool2 .visibility == "public " # Updated from gateway
1051+ assert existing_tool2 .visibility == "private " # Visibility NOT updated from gateway because context is NOT update
10521052
10531053 @pytest .mark .asyncio
10541054 async def test_helper_methods_empty_input_lists (self ):
@@ -1277,7 +1277,7 @@ async def test_helper_methods_tool_removal_scenario(self):
12771277 # existing_tool1 should be updated with gateway values (even if description stays the same)
12781278 assert existing_tool1 .url == "http://new.com" # Updated from gateway
12791279 assert existing_tool1 .auth_type == "bearer" # Updated from gateway
1280- assert existing_tool1 .visibility == "public " # Updated from gateway
1280+ assert existing_tool1 .visibility == "private " # Visibility NOT updated from gateway because context is NOT update
12811281
12821282 # existing_tool3 should be updated (description not customized, so upstream value applies)
12831283 assert existing_tool3 .description == "Updated description"
@@ -1350,13 +1350,13 @@ async def test_helper_methods_resource_removal_scenario(self):
13501350
13511351 # existing_resource1 should be updated with gateway values
13521352 assert existing_resource1 .description == "Keep this resource"
1353- assert existing_resource1 .visibility == "public " # Updated from gateway
1353+ assert existing_resource1 .visibility == "private " # Visibility NOT updated from gateway because context is NOT update
13541354
13551355 # existing_resource3 should be updated
13561356 assert existing_resource3 .description == "Updated description"
13571357 assert existing_resource3 .mime_type == "application/json"
13581358 assert existing_resource3 .uri_template == "new template"
1359- assert existing_resource3 .visibility == "public " # Updated from gateway
1359+ assert existing_resource3 .visibility == "private " # Visibility NOT updated from gateway because context is NOT update
13601360
13611361 @pytest .mark .asyncio
13621362 async def test_helper_methods_prompt_removal_scenario (self ):
@@ -1416,12 +1416,12 @@ async def test_helper_methods_prompt_removal_scenario(self):
14161416 # existing_prompt1 should be updated with gateway values
14171417 assert existing_prompt1 .description == "Keep this prompt"
14181418 assert existing_prompt1 .template == "Keep template"
1419- assert existing_prompt1 .visibility == "public " # Updated from gateway
1419+ assert existing_prompt1 .visibility == "private " # Visibility NOT updated from gateway because context is NOT update
14201420
14211421 # existing_prompt3 should be updated
14221422 assert existing_prompt3 .description == "Updated description"
14231423 assert existing_prompt3 .template == "Updated template"
1424- assert existing_prompt3 .visibility == "public " # Updated from gateway
1424+ assert existing_prompt3 .visibility == "private " # Visibility NOT updated from gateway because context is NOT update
14251425
14261426 @pytest .mark .asyncio
14271427 async def test_fetch_tools_after_oauth_prompt_stale_removal_uses_original_name (self ):
@@ -1536,3 +1536,139 @@ async def test_helper_methods_complete_removal_scenario(self):
15361536 assert tools_to_remove [0 ].original_name == "old_tool"
15371537 assert resources_to_remove [0 ].uri == "file:///old.txt"
15381538 assert prompts_to_remove [0 ].name == "old_prompt"
1539+
1540+ @pytest .mark .asyncio
1541+ async def test_update_visibility_logic (self ):
1542+ """Test that existing items retain visibility on auto-refresh, but inherit on manual update."""
1543+ service = GatewayService ()
1544+ mock_db = MagicMock ()
1545+ mock_gateway = MagicMock ()
1546+ mock_gateway .id = "gw"
1547+ mock_gateway .url = "http://gw.com"
1548+ mock_gateway .auth_type = "none"
1549+ mock_gateway .visibility = "public"
1550+
1551+ # Mock tools
1552+ existing_tool = MagicMock ()
1553+ existing_tool .original_name = "test_tool"
1554+ existing_tool .description = "Test Tool"
1555+ existing_tool .original_description = "Test Tool"
1556+ existing_tool .visibility = "private"
1557+
1558+ tool_from_server = MagicMock ()
1559+ tool_from_server .name = "test_tool"
1560+ tool_from_server .description = "Test Tool"
1561+
1562+ # Mock resources
1563+ existing_res = MagicMock ()
1564+ existing_res .uri = "file:///test"
1565+ existing_res .name = "test"
1566+ existing_res .description = "Test Res"
1567+ existing_res .visibility = "team"
1568+
1569+ res_from_server = MagicMock ()
1570+ res_from_server .uri = "file:///test"
1571+ res_from_server .name = "test"
1572+ res_from_server .description = "Test Res"
1573+
1574+ # Mock prompts
1575+ existing_prompt = MagicMock ()
1576+ existing_prompt .original_name = "test_prompt"
1577+ existing_prompt .name = "test_prompt"
1578+ existing_prompt .description = "Test Prompt"
1579+ existing_prompt .visibility = "private"
1580+
1581+ prompt_from_server = MagicMock ()
1582+ prompt_from_server .name = "test_prompt"
1583+ prompt_from_server .description = "Test Prompt"
1584+
1585+ # --- Test 1: AUTO REFRESH Context ---
1586+ def create_mock_result (item ):
1587+ mock_result = MagicMock ()
1588+ mock_result .scalars .return_value .all .return_value = [item ]
1589+ return mock_result
1590+
1591+ # Reset visibilities
1592+ existing_tool .visibility = "private"
1593+ existing_res .visibility = "team"
1594+ existing_prompt .visibility = "private"
1595+
1596+ mock_db .execute .side_effect = [
1597+ create_mock_result (existing_tool ),
1598+ ]
1599+ service ._update_or_create_tools (mock_db , [tool_from_server ], mock_gateway , "auto_refresh" )
1600+ assert existing_tool .visibility == "private"
1601+
1602+ mock_db .execute .side_effect = [
1603+ create_mock_result (existing_res ),
1604+ ]
1605+ service ._update_or_create_resources (mock_db , [res_from_server ], mock_gateway , "health_check" )
1606+ assert existing_res .visibility == "team"
1607+
1608+ mock_db .execute .side_effect = [
1609+ create_mock_result (existing_prompt ),
1610+ ]
1611+ service ._update_or_create_prompts (mock_db , [prompt_from_server ], mock_gateway , "rediscovery" )
1612+ assert existing_prompt .visibility == "private"
1613+
1614+ # --- Test 2: MANUAL UPDATE with explicit visibility change ---
1615+ mock_db .execute .side_effect = [
1616+ create_mock_result (existing_tool ),
1617+ ]
1618+ service ._update_or_create_tools (mock_db , [tool_from_server ], mock_gateway , "update" , update_visibility = True )
1619+ assert existing_tool .visibility == "public"
1620+
1621+ mock_db .execute .side_effect = [
1622+ create_mock_result (existing_res ),
1623+ ]
1624+ service ._update_or_create_resources (mock_db , [res_from_server ], mock_gateway , "update" , update_visibility = True )
1625+ assert existing_res .visibility == "public"
1626+
1627+ mock_db .execute .side_effect = [
1628+ create_mock_result (existing_prompt ),
1629+ ]
1630+ service ._update_or_create_prompts (mock_db , [prompt_from_server ], mock_gateway , "update" , update_visibility = True )
1631+ assert existing_prompt .visibility == "public"
1632+
1633+ # --- Test 3: UPDATE without visibility change (e.g. description-only edit) ---
1634+ # Visibility must NOT be overwritten even though created_via is "update"
1635+ existing_tool .visibility = "private"
1636+ existing_res .visibility = "team"
1637+ existing_prompt .visibility = "private"
1638+
1639+ mock_db .execute .side_effect = [create_mock_result (existing_tool )]
1640+ service ._update_or_create_tools (mock_db , [tool_from_server ], mock_gateway , "update" , update_visibility = False )
1641+ assert existing_tool .visibility == "private"
1642+
1643+ mock_db .execute .side_effect = [create_mock_result (existing_res )]
1644+ service ._update_or_create_resources (mock_db , [res_from_server ], mock_gateway , "update" , update_visibility = False )
1645+ assert existing_res .visibility == "team"
1646+
1647+ mock_db .execute .side_effect = [create_mock_result (existing_prompt )]
1648+ service ._update_or_create_prompts (mock_db , [prompt_from_server ], mock_gateway , "update" , update_visibility = False )
1649+ assert existing_prompt .visibility == "private"
1650+
1651+ def test_create_db_tool_inherits_gateway_visibility (self ):
1652+ """New tools created via _create_db_tool inherit visibility from the gateway, not hardcoded 'public'."""
1653+ service = GatewayService ()
1654+ tool = MagicMock ()
1655+ tool .name = "new_tool"
1656+ tool .description = "A tool"
1657+ tool .request_type = "POST"
1658+ tool .headers = {}
1659+ tool .input_schema = {}
1660+ tool .annotations = {}
1661+ tool .jsonpath_filter = None
1662+
1663+ for vis in ("private" , "team" , "public" ):
1664+ gateway = MagicMock ()
1665+ gateway .url = "http://gw.com"
1666+ gateway .name = "gw"
1667+ gateway .auth_type = "none"
1668+ gateway .auth_value = None
1669+ gateway .team_id = "t1"
1670+ gateway .owner_email = "owner@example.com"
1671+ gateway .visibility = vis
1672+
1673+ db_tool = service ._create_db_tool (tool = tool , gateway = gateway )
1674+ assert db_tool .visibility == vis , f"Expected { vis } , got { db_tool .visibility } "
0 commit comments