Skip to content

Commit 7bad6d3

Browse files
Testclaude
andcommitted
fix(ci): use file output instead of MEMORY for NScurl POST
- NScurl MEMORY mode returns status "OK", not response body - Changed to write response directly to file for proper JSON parsing - Added file existence check before nsJSON parsing 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent e0c73d3 commit 7bad6d3

3 files changed

Lines changed: 140 additions & 19 deletions

File tree

.github/workflows/release-mcpb.yml

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -448,30 +448,25 @@ jobs:
448448
FileWrite $8 "Server: __SERVER_URL__$\r$\n"
449449
FileWrite $8 "Username: __USERNAME__$\r$\n"
450450
451-
; Step 1: NScurl POST to /auth/login
452-
FileWrite $8 "Step 1: NScurl POST to /auth/login...$\r$\n"
451+
; Step 1: NScurl POST to /auth/login - write directly to file (not MEMORY)
452+
FileWrite $8 "Step 1: NScurl POST to /auth/login (output to file)...$\r$\n"
453453
454-
NScurl::http POST "__SERVER_URL__/auth/login" MEMORY /HEADER "Content-Type: application/json" /DATA '{"username":"__USERNAME__","password":"__PASSWORD__"}' /INSIST /TIMEOUT 30000 /END
454+
NScurl::http POST "__SERVER_URL__/auth/login" "$1" /HEADER "Content-Type: application/json" /DATA '{"username":"__USERNAME__","password":"__PASSWORD__"}' /INSIST /TIMEOUT 30000 /END
455455
Pop $0
456456
457-
FileWrite $8 "NScurl returned: [$0]$\r$\n"
457+
FileWrite $8 "NScurl status: [$0]$\r$\n"
458458
459-
; Check if request succeeded
460-
StrCpy $2 $0 12
461-
StrCmp $2 "https://http" fail_nscurl
462-
StrCmp $0 "" fail_nscurl
459+
; Check if request succeeded - "OK" means success for file output
460+
StrCmp $0 "OK" +3
461+
FileWrite $8 "FAIL: NScurl returned error: $0$\r$\n"
462+
Goto fail_nscurl
463463
464-
; Check for error responses
465-
StrCpy $2 $0 5
466-
StrCmp $2 "Error" fail_nscurl
467-
468-
; Step 2: Write response to file for nsJSON parsing
469-
FileWrite $8 "Step 2: Writing response to file...$\r$\n"
470-
FileOpen $7 $1 w
471-
FileWrite $7 $0
472-
FileClose $7
464+
; Verify file was created
465+
IfFileExists $1 +3
466+
FileWrite $8 "FAIL: Response file not created$\r$\n"
467+
Goto fail_nscurl
473468
474-
FileWrite $8 "Response written to: $1$\r$\n"
469+
FileWrite $8 "Response saved to: $1$\r$\n"
475470
476471
; Step 3: Parse with nsJSON
477472
FileWrite $8 "Step 3: Parsing with nsJSON::Set /file...$\r$\n"

src/code_indexer/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,5 @@
66
HNSW graph indexing (O(log N) complexity).
77
"""
88

9-
__version__ = "8.4.36"
9+
__version__ = "8.4.37"
1010
__author__ = "Seba Battig"
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
"""Tests for context-aware error suggestions in MCP handlers."""
2+
3+
import pytest
4+
from unittest.mock import patch, Mock
5+
from datetime import datetime
6+
import json
7+
8+
9+
class TestErrorWithSuggestions:
10+
"""Test the error suggestion helper function."""
11+
12+
def test_fuzzy_match_typo(self):
13+
"""Test that typos get matched to correct values."""
14+
from code_indexer.server.mcp.handlers import _error_with_suggestions
15+
16+
result = _error_with_suggestions(
17+
error_msg="Repository not found",
18+
attempted_value="evolution-gloabl", # typo: gloabl -> global
19+
available_values=["evolution-global", "backend-global", "frontend-global"],
20+
)
21+
22+
assert result["success"] is False
23+
assert result["error"] == "Repository not found"
24+
assert "evolution-global" in result["suggestions"]
25+
assert len(result["suggestions"]) <= 3
26+
assert "available_values" in result
27+
28+
def test_no_match_returns_empty_suggestions(self):
29+
"""Test that completely wrong value returns empty suggestions."""
30+
from code_indexer.server.mcp.handlers import _error_with_suggestions
31+
32+
result = _error_with_suggestions(
33+
error_msg="Repository not found",
34+
attempted_value="xyzabc123", # no match
35+
available_values=["evolution-global", "backend-global"],
36+
)
37+
38+
assert result["suggestions"] == []
39+
assert len(result["available_values"]) > 0
40+
41+
def test_available_values_limited(self):
42+
"""Test that available_values are limited to prevent huge responses."""
43+
from code_indexer.server.mcp.handlers import _error_with_suggestions
44+
45+
many_repos = [f"repo-{i}-global" for i in range(50)]
46+
result = _error_with_suggestions(
47+
error_msg="Not found",
48+
attempted_value="repo-1-global",
49+
available_values=many_repos,
50+
)
51+
52+
assert len(result["available_values"]) <= 10
53+
54+
55+
class TestGetAvailableRepos:
56+
"""Test the available repos helper."""
57+
58+
def test_returns_repo_list(self):
59+
"""Test that available repos are returned."""
60+
from code_indexer.server.mcp.handlers import _get_available_repos
61+
62+
with patch("code_indexer.server.mcp.handlers._get_golden_repos_dir") as mock_dir:
63+
mock_dir.return_value = "/fake/path"
64+
with patch("code_indexer.server.mcp.handlers.GlobalRegistry") as mock_reg:
65+
mock_instance = Mock()
66+
mock_instance.list_global_repos.return_value = [
67+
{"alias_name": "evolution-global"},
68+
{"alias_name": "backend-global"},
69+
]
70+
mock_reg.return_value = mock_instance
71+
72+
result = _get_available_repos()
73+
74+
assert result == ["evolution-global", "backend-global"]
75+
76+
def test_returns_empty_on_error(self):
77+
"""Test graceful handling of registry errors."""
78+
from code_indexer.server.mcp.handlers import _get_available_repos
79+
80+
with patch("code_indexer.server.mcp.handlers._get_golden_repos_dir") as mock_dir:
81+
mock_dir.side_effect = RuntimeError("No golden_repos_dir")
82+
83+
result = _get_available_repos()
84+
85+
assert result == []
86+
87+
88+
class TestSearchCodeErrorSuggestions:
89+
"""Test that search_code returns suggestions on repo not found."""
90+
91+
@pytest.mark.asyncio
92+
async def test_search_code_repo_not_found_has_suggestions(self):
93+
"""Test search_code returns suggestions when repo not found."""
94+
from code_indexer.server.mcp.handlers import search_code
95+
from code_indexer.server.auth.user_manager import User, UserRole
96+
97+
user = User(
98+
username="testuser",
99+
password_hash="hash",
100+
role=UserRole.NORMAL_USER,
101+
created_at=datetime.now(),
102+
)
103+
104+
params = {
105+
"query_text": "test",
106+
"repository_alias": "evolution-gloabl", # typo
107+
}
108+
109+
with patch("code_indexer.server.mcp.handlers._get_golden_repos_dir") as mock_dir:
110+
mock_dir.return_value = "/fake/path"
111+
with patch("code_indexer.server.mcp.handlers.GlobalRegistry") as mock_reg:
112+
mock_instance = Mock()
113+
mock_instance.list_global_repos.return_value = [
114+
{"alias_name": "evolution-global"},
115+
{"alias_name": "backend-global"},
116+
]
117+
mock_reg.return_value = mock_instance
118+
119+
result = await search_code(params, user)
120+
121+
response_data = json.loads(result["content"][0]["text"])
122+
123+
assert response_data["success"] is False
124+
assert "suggestions" in response_data
125+
assert "evolution-global" in response_data["suggestions"]
126+
assert "available_values" in response_data

0 commit comments

Comments
 (0)