1+ """Tests for data sources tool."""
2+
3+ import json
4+ from unittest .mock import AsyncMock , MagicMock , patch
5+
6+ import pytest
7+ from fastmcp import Context
8+
9+ from tools .datasources import get_data_sources
10+
11+
12+ @pytest .mark .asyncio
13+ @patch ('tools.datasources.get_api_key_from_context' )
14+ async def test_get_data_sources_removes_repository_ids_from_workspaces (mock_get_api_key ):
15+ """Test that repositoryIds are removed from workspace data sources."""
16+ mock_get_api_key .return_value = "test-key"
17+
18+ # Mock context
19+ mock_ctx = MagicMock (spec = Context )
20+ mock_ctx .info = AsyncMock ()
21+ mock_ctx .warning = AsyncMock ()
22+ mock_ctx .error = AsyncMock ()
23+
24+ mock_lifespan_context = MagicMock ()
25+ mock_lifespan_context .base_url = "https://api.example.com"
26+
27+ # Mock client with response containing workspaces with repositoryIds
28+ mock_response = MagicMock ()
29+ mock_response .json .return_value = [
30+ {
31+ "id" : "repo-1" ,
32+ "name" : "Test Repository" ,
33+ "type" : "Repository" ,
34+ "url" : "https://github.com/example/repo" ,
35+ "state" : "Alive"
36+ },
37+ {
38+ "id" : "workspace-1" ,
39+ "name" : "Test Workspace" ,
40+ "type" : "Workspace" ,
41+ "repositoryIds" : ["repo-1" , "repo-2" , "repo-3" ],
42+ "state" : "Alive"
43+ }
44+ ]
45+ mock_response .raise_for_status = MagicMock ()
46+
47+ mock_client = AsyncMock ()
48+ mock_client .get = AsyncMock (return_value = mock_response )
49+ mock_lifespan_context .client = mock_client
50+
51+ mock_ctx .request_context .lifespan_context = mock_lifespan_context
52+
53+ # Call the function
54+ result = await get_data_sources (mock_ctx , alive_only = True )
55+
56+ # Parse the result to verify repositoryIds were removed
57+ # The result format is: "Available data sources:\n{json}\n\nYou can use..."
58+ json_start = result .index ('[' )
59+ json_end = result .rindex (']' ) + 1
60+ data_sources = json .loads (result [json_start :json_end ])
61+
62+ # Verify repository still has all fields
63+ repo = next (ds for ds in data_sources if ds ["type" ] == "Repository" )
64+ assert repo ["id" ] == "repo-1"
65+ assert repo ["name" ] == "Test Repository"
66+ assert repo ["url" ] == "https://github.com/example/repo"
67+ assert "repositoryIds" not in repo
68+
69+ # Verify workspace has repositoryIds removed
70+ workspace = next (ds for ds in data_sources if ds ["type" ] == "Workspace" )
71+ assert workspace ["id" ] == "workspace-1"
72+ assert workspace ["name" ] == "Test Workspace"
73+ assert "repositoryIds" not in workspace , "repositoryIds should be removed from workspace"
74+
75+ # Verify API was called correctly
76+ mock_client .get .assert_called_once_with (
77+ "/api/datasources/alive" ,
78+ headers = {"Authorization" : "Bearer test-key" }
79+ )
80+
81+
82+ @pytest .mark .asyncio
83+ @patch ('tools.datasources.get_api_key_from_context' )
84+ async def test_get_data_sources_preserves_other_workspace_fields (mock_get_api_key ):
85+ """Test that other workspace fields are preserved when removing repositoryIds."""
86+ mock_get_api_key .return_value = "test-key"
87+
88+ # Mock context
89+ mock_ctx = MagicMock (spec = Context )
90+ mock_ctx .info = AsyncMock ()
91+ mock_ctx .warning = AsyncMock ()
92+ mock_ctx .error = AsyncMock ()
93+
94+ mock_lifespan_context = MagicMock ()
95+ mock_lifespan_context .base_url = "https://api.example.com"
96+
97+ # Mock client with workspace containing various fields
98+ mock_response = MagicMock ()
99+ mock_response .json .return_value = [
100+ {
101+ "id" : "workspace-1" ,
102+ "name" : "Test Workspace" ,
103+ "type" : "Workspace" ,
104+ "state" : "Alive" ,
105+ "repositoryIds" : ["repo-1" , "repo-2" ],
106+ "customField" : "custom-value" ,
107+ "createdAt" : "2025-01-01T00:00:00Z"
108+ }
109+ ]
110+ mock_response .raise_for_status = MagicMock ()
111+
112+ mock_client = AsyncMock ()
113+ mock_client .get = AsyncMock (return_value = mock_response )
114+ mock_lifespan_context .client = mock_client
115+
116+ mock_ctx .request_context .lifespan_context = mock_lifespan_context
117+
118+ # Call the function
119+ result = await get_data_sources (mock_ctx , alive_only = True )
120+
121+ # Parse the result
122+ json_start = result .index ('[' )
123+ json_end = result .rindex (']' ) + 1
124+ data_sources = json .loads (result [json_start :json_end ])
125+
126+ workspace = data_sources [0 ]
127+
128+ # Verify repositoryIds removed but other fields preserved
129+ assert "repositoryIds" not in workspace
130+ assert workspace ["id" ] == "workspace-1"
131+ assert workspace ["name" ] == "Test Workspace"
132+ assert workspace ["type" ] == "Workspace"
133+ assert workspace ["state" ] == "Alive"
134+ assert workspace ["customField" ] == "custom-value"
135+ assert workspace ["createdAt" ] == "2025-01-01T00:00:00Z"
136+
137+
138+ @pytest .mark .asyncio
139+ @patch ('tools.datasources.get_api_key_from_context' )
140+ async def test_get_data_sources_handles_missing_repository_ids (mock_get_api_key ):
141+ """Test that function handles workspaces without repositoryIds field."""
142+ mock_get_api_key .return_value = "test-key"
143+
144+ # Mock context
145+ mock_ctx = MagicMock (spec = Context )
146+ mock_ctx .info = AsyncMock ()
147+ mock_ctx .warning = AsyncMock ()
148+ mock_ctx .error = AsyncMock ()
149+
150+ mock_lifespan_context = MagicMock ()
151+ mock_lifespan_context .base_url = "https://api.example.com"
152+
153+ # Mock client with workspace without repositoryIds
154+ mock_response = MagicMock ()
155+ mock_response .json .return_value = [
156+ {
157+ "id" : "workspace-1" ,
158+ "name" : "Test Workspace" ,
159+ "type" : "Workspace" ,
160+ "state" : "Alive"
161+ }
162+ ]
163+ mock_response .raise_for_status = MagicMock ()
164+
165+ mock_client = AsyncMock ()
166+ mock_client .get = AsyncMock (return_value = mock_response )
167+ mock_lifespan_context .client = mock_client
168+
169+ mock_ctx .request_context .lifespan_context = mock_lifespan_context
170+
171+ # Call the function - should not raise an error
172+ result = await get_data_sources (mock_ctx , alive_only = True )
173+
174+ # Parse the result
175+ json_start = result .index ('[' )
176+ json_end = result .rindex (']' ) + 1
177+ data_sources = json .loads (result [json_start :json_end ])
178+
179+ # Verify workspace is intact
180+ workspace = data_sources [0 ]
181+ assert workspace ["id" ] == "workspace-1"
182+ assert workspace ["name" ] == "Test Workspace"
183+ assert "repositoryIds" not in workspace
0 commit comments