@@ -18,10 +18,14 @@ def client():
1818 return TestClient (create_app (config ), raise_server_exceptions = False )
1919
2020
21+ # =============================================================================
22+ # Query Parameter Tests (Preferred Method)
23+ # =============================================================================
24+
25+
2126@pytest .mark .asyncio
22- async def test_git_changes_success (client ):
23- """Test successful git changes endpoint."""
24- # Mock the get_git_changes function
27+ async def test_git_changes_query_param_success (client ):
28+ """Test successful git changes endpoint with query parameter."""
2529 expected_changes = [
2630 GitChange (status = GitChangeStatus .ADDED , path = Path ("new_file.py" )),
2731 GitChange (status = GitChangeStatus .UPDATED , path = Path ("existing_file.py" )),
@@ -32,12 +36,11 @@ async def test_git_changes_success(client):
3236 mock_git_changes .return_value = expected_changes
3337
3438 test_path = "src/test_repo"
35- response = client .get (f "/api/git/changes/ { test_path } " )
39+ response = client .get ("/api/git/changes" , params = { "path" : test_path })
3640
3741 assert response .status_code == 200
3842 response_data = response .json ()
3943
40- # Verify the response structure
4144 assert len (response_data ) == 3
4245 assert response_data [0 ]["status" ] == "ADDED"
4346 assert response_data [0 ]["path" ] == "new_file.py"
@@ -46,40 +49,64 @@ async def test_git_changes_success(client):
4649 assert response_data [2 ]["status" ] == "DELETED"
4750 assert response_data [2 ]["path" ] == "old_file.py"
4851
49- # Verify the mock was called correctly
5052 mock_git_changes .assert_called_once_with (Path (test_path ))
5153
5254
5355@pytest .mark .asyncio
54- async def test_git_changes_empty_result (client ):
55- """Test git changes endpoint with no changes."""
56+ async def test_git_changes_query_param_empty_result (client ):
57+ """Test git changes endpoint with query parameter and no changes."""
5658 with patch ("openhands.agent_server.git_router.get_git_changes" ) as mock_git_changes :
5759 mock_git_changes .return_value = []
5860
5961 test_path = "src/empty_repo"
60- response = client .get (f "/api/git/changes/ { test_path } " )
62+ response = client .get ("/api/git/changes" , params = { "path" : test_path })
6163
6264 assert response .status_code == 200
6365 assert response .json () == []
6466
6567
6668@pytest .mark .asyncio
67- async def test_git_changes_with_exception (client ):
68- """Test git changes endpoint when git operation fails."""
69+ async def test_git_changes_query_param_with_exception (client ):
70+ """Test git changes endpoint with query parameter when git operation fails."""
6971 with patch ("openhands.agent_server.git_router.get_git_changes" ) as mock_git_changes :
7072 mock_git_changes .side_effect = Exception ("Git repository not found" )
7173
7274 test_path = "nonexistent/repo"
73- response = client .get (f "/api/git/changes/ { test_path } " )
75+ response = client .get ("/api/git/changes" , params = { "path" : test_path })
7476
75- # Should return 500 due to exception
7677 assert response .status_code == 500
7778
7879
7980@pytest .mark .asyncio
80- async def test_git_diff_success (client ):
81- """Test successful git diff endpoint."""
82- # Mock the get_git_diff function
81+ async def test_git_changes_missing_path_param (client ):
82+ """Test git changes endpoint returns 422 when path parameter is missing."""
83+ response = client .get ("/api/git/changes" )
84+
85+ assert response .status_code == 422
86+
87+
88+ @pytest .mark .asyncio
89+ async def test_git_changes_query_param_absolute_path (client ):
90+ """Test git changes with query parameter and absolute path (main fix use case)."""
91+ expected_changes = [
92+ GitChange (status = GitChangeStatus .ADDED , path = Path ("new_file.py" )),
93+ ]
94+
95+ with patch ("openhands.agent_server.git_router.get_git_changes" ) as mock_git_changes :
96+ mock_git_changes .return_value = expected_changes
97+
98+ # This is the main use case - absolute paths with leading slash
99+ test_path = "/workspace/project"
100+ response = client .get ("/api/git/changes" , params = {"path" : test_path })
101+
102+ assert response .status_code == 200
103+ assert len (response .json ()) == 1
104+ mock_git_changes .assert_called_once_with (Path (test_path ))
105+
106+
107+ @pytest .mark .asyncio
108+ async def test_git_diff_query_param_success (client ):
109+ """Test successful git diff endpoint with query parameter."""
83110 expected_diff = GitDiff (
84111 modified = "def new_function():\n return 'updated'" ,
85112 original = "def old_function():\n return 'original'" ,
@@ -89,74 +116,141 @@ async def test_git_diff_success(client):
89116 mock_git_diff .return_value = expected_diff
90117
91118 test_path = "src/test_file.py"
92- response = client .get (f "/api/git/diff/ { test_path } " )
119+ response = client .get ("/api/git/diff" , params = { "path" : test_path })
93120
94121 assert response .status_code == 200
95122 response_data = response .json ()
96123
97- # Verify the response structure
98124 assert response_data ["modified" ] == expected_diff .modified
99125 assert response_data ["original" ] == expected_diff .original
100-
101- # Verify the mock was called correctly (now expects Path object)
102126 mock_git_diff .assert_called_once_with (Path (test_path ))
103127
104128
105129@pytest .mark .asyncio
106- async def test_git_diff_with_none_values (client ):
107- """Test git diff endpoint with None values."""
108- # Mock the get_git_diff function with None values
130+ async def test_git_diff_query_param_with_none_values (client ):
131+ """Test git diff endpoint with query parameter and None values."""
109132 expected_diff = GitDiff (modified = None , original = None )
110133
111134 with patch ("openhands.agent_server.git_router.get_git_diff" ) as mock_git_diff :
112135 mock_git_diff .return_value = expected_diff
113136
114137 test_path = "nonexistent_file.py"
115- response = client .get (f "/api/git/diff/ { test_path } " )
138+ response = client .get ("/api/git/diff" , params = { "path" : test_path })
116139
117140 assert response .status_code == 200
118141 response_data = response .json ()
119142
120- # Verify the response structure
121143 assert response_data ["modified" ] is None
122144 assert response_data ["original" ] is None
123145
124146
125147@pytest .mark .asyncio
126- async def test_git_diff_with_exception (client ):
127- """Test git diff endpoint when git operation fails."""
148+ async def test_git_diff_query_param_with_exception (client ):
149+ """Test git diff endpoint with query parameter when git operation fails."""
128150 with patch ("openhands.agent_server.git_router.get_git_diff" ) as mock_git_diff :
129151 mock_git_diff .side_effect = Exception ("Git diff failed" )
130152
131153 test_path = "nonexistent/file.py"
132- response = client .get (f "/api/git/diff/ { test_path } " )
154+ response = client .get ("/api/git/diff" , params = { "path" : test_path })
133155
134- # Should return 500 due to exception
135156 assert response .status_code == 500
136157
137158
138159@pytest .mark .asyncio
139- async def test_git_diff_nested_path (client ):
140- """Test git diff endpoint with nested file path."""
141- expected_diff = GitDiff (modified = "updated content" , original = "original content" )
160+ async def test_git_diff_missing_path_param (client ):
161+ """Test git diff endpoint returns 422 when path parameter is missing."""
162+ response = client .get ("/api/git/diff" )
163+
164+ assert response .status_code == 422
165+
166+
167+ # =============================================================================
168+ # Path Parameter Tests (Legacy/Backwards Compatibility)
169+ # =============================================================================
170+
171+
172+ @pytest .mark .asyncio
173+ async def test_git_changes_path_param_success (client ):
174+ """Test git changes endpoint with path parameter (legacy)."""
175+ expected_changes = [
176+ GitChange (status = GitChangeStatus .ADDED , path = Path ("new_file.py" )),
177+ GitChange (status = GitChangeStatus .UPDATED , path = Path ("existing_file.py" )),
178+ ]
179+
180+ with patch ("openhands.agent_server.git_router.get_git_changes" ) as mock_git_changes :
181+ mock_git_changes .return_value = expected_changes
182+
183+ test_path = "src/test_repo"
184+ response = client .get (f"/api/git/changes/{ test_path } " )
185+
186+ assert response .status_code == 200
187+ response_data = response .json ()
188+
189+ assert len (response_data ) == 2
190+ assert response_data [0 ]["status" ] == "ADDED"
191+ assert response_data [1 ]["status" ] == "UPDATED"
192+ mock_git_changes .assert_called_once_with (Path (test_path ))
193+
194+
195+ @pytest .mark .asyncio
196+ async def test_git_changes_path_param_nested (client ):
197+ """Test git changes endpoint with nested path parameter."""
198+ expected_changes = [
199+ GitChange (status = GitChangeStatus .ADDED , path = Path ("file.py" )),
200+ ]
201+
202+ with patch ("openhands.agent_server.git_router.get_git_changes" ) as mock_git_changes :
203+ mock_git_changes .return_value = expected_changes
204+
205+ test_path = "src/deep/nested/repo"
206+ response = client .get (f"/api/git/changes/{ test_path } " )
207+
208+ assert response .status_code == 200
209+ mock_git_changes .assert_called_once_with (Path (test_path ))
210+
211+
212+ @pytest .mark .asyncio
213+ async def test_git_diff_path_param_success (client ):
214+ """Test git diff endpoint with path parameter (legacy)."""
215+ expected_diff = GitDiff (modified = "new content" , original = "old content" )
142216
143217 with patch ("openhands.agent_server.git_router.get_git_diff" ) as mock_git_diff :
144218 mock_git_diff .return_value = expected_diff
145219
146- # Test with nested path
147- test_path = "src/utils/helper.py"
220+ test_path = "src/test_file.py"
148221 response = client .get (f"/api/git/diff/{ test_path } " )
149222
150223 assert response .status_code == 200
224+ response_data = response .json ()
151225
152- # Verify the correct path was passed (now expects Path object)
226+ assert response_data ["modified" ] == "new content"
227+ assert response_data ["original" ] == "old content"
153228 mock_git_diff .assert_called_once_with (Path (test_path ))
154229
155230
231+ @pytest .mark .asyncio
232+ async def test_git_diff_path_param_nested (client ):
233+ """Test git diff endpoint with nested path parameter."""
234+ expected_diff = GitDiff (modified = "updated" , original = "original" )
235+
236+ with patch ("openhands.agent_server.git_router.get_git_diff" ) as mock_git_diff :
237+ mock_git_diff .return_value = expected_diff
238+
239+ test_path = "src/utils/helper.py"
240+ response = client .get (f"/api/git/diff/{ test_path } " )
241+
242+ assert response .status_code == 200
243+ mock_git_diff .assert_called_once_with (Path (test_path ))
244+
245+
246+ # =============================================================================
247+ # Additional Edge Case Tests
248+ # =============================================================================
249+
250+
156251@pytest .mark .asyncio
157252async def test_git_changes_with_all_status_types (client ):
158253 """Test git changes endpoint with all possible GitChangeStatus values."""
159- # Test all possible status types
160254 expected_changes = [
161255 GitChange (status = GitChangeStatus .ADDED , path = Path ("added.py" )),
162256 GitChange (status = GitChangeStatus .UPDATED , path = Path ("updated.py" )),
@@ -168,7 +262,7 @@ async def test_git_changes_with_all_status_types(client):
168262 mock_git_changes .return_value = expected_changes
169263
170264 test_path = "src/test_repo"
171- response = client .get (f "/api/git/changes/ { test_path } " )
265+ response = client .get ("/api/git/changes" , params = { "path" : test_path })
172266
173267 assert response .status_code == 200
174268 response_data = response .json ()
@@ -183,7 +277,6 @@ async def test_git_changes_with_all_status_types(client):
183277@pytest .mark .asyncio
184278async def test_git_changes_with_complex_paths (client ):
185279 """Test git changes endpoint with complex file paths."""
186- # Test with various path complexities
187280 expected_changes = [
188281 GitChange (
189282 status = GitChangeStatus .ADDED ,
@@ -203,7 +296,7 @@ async def test_git_changes_with_complex_paths(client):
203296 mock_git_changes .return_value = expected_changes
204297
205298 test_path = "src/complex_repo"
206- response = client .get (f "/api/git/changes/ { test_path } " )
299+ response = client .get ("/api/git/changes" , params = { "path" : test_path })
207300
208301 assert response .status_code == 200
209302 response_data = response .json ()
0 commit comments