Skip to content

Commit 5666642

Browse files
Yevhenii-YatchenkoGerrit Code Review
authored andcommitted
Merge "Add /a/ prefix to Gerrit API URLs for authenticated access"
2 parents 441687e + 5f764bf commit 5666642

4 files changed

Lines changed: 108 additions & 2 deletions

File tree

gerrit_mcp_server/gerrit_urls.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ def get_curl_command_for_gerrit_url(
3434
stripped_gerrit_base_url = (
3535
gerrit_base_url.replace("https://", "").replace("http://", "").rstrip("/")
3636
)
37+
# Strip /a suffix that may have been added by _normalize_gerrit_url
38+
# for authenticated access, so we can match against configured host URLs.
39+
if stripped_gerrit_base_url.endswith("/a"):
40+
stripped_gerrit_base_url = stripped_gerrit_base_url[:-2]
3741

3842
for host in gerrit_hosts:
3943
internal_url = (

gerrit_mcp_server/main.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,16 @@ def _get_gerrit_base_url(gerrit_base_url: Optional[str] = None) -> str:
119119
)
120120

121121

122+
def _apply_authenticated_prefix(url: str, host: Dict[str, Any]) -> str:
123+
"""Appends /a to the URL if the host's auth type requires it for API access."""
124+
auth_type = host.get("authentication", {}).get("type")
125+
126+
if auth_type in ("http_basic", "git_cookies"):
127+
return url + "/a"
128+
129+
return url
130+
131+
122132
def _normalize_gerrit_url(url: str, gerrit_hosts: List[Dict[str, Any]]) -> str:
123133
"""Normalizes a Gerrit URL based on the mappings in the provided gerrit_hosts."""
124134

@@ -127,6 +137,7 @@ def _normalize_gerrit_url(url: str, gerrit_hosts: List[Dict[str, Any]]) -> str:
127137
stripped_original_url = original_url.replace("https://", "").replace("http://", "")
128138

129139
normalized_url = url # Default to original if no match found
140+
matched_host = None
130141

131142
for host in gerrit_hosts:
132143
internal_url = host.get("internal_url")
@@ -152,6 +163,7 @@ def _normalize_gerrit_url(url: str, gerrit_hosts: List[Dict[str, Any]]) -> str:
152163
normalized_url = external_url
153164
elif internal_url:
154165
normalized_url = internal_url
166+
matched_host = host
155167
break # Found a match, so we can exit the loop.
156168

157169
# Ensure https, then strip trailing slash
@@ -162,7 +174,12 @@ def _normalize_gerrit_url(url: str, gerrit_hosts: List[Dict[str, Any]]) -> str:
162174
elif normalized_url.startswith("http://"):
163175
normalized_url = normalized_url.replace("http://", "https://")
164176

165-
return normalized_url.rstrip("/")
177+
normalized_url = normalized_url.rstrip("/")
178+
179+
if matched_host:
180+
return _apply_authenticated_prefix(normalized_url, matched_host)
181+
182+
return normalized_url
166183

167184

168185
async def run_curl(args: List[str], gerrit_base_url: str) -> str:
@@ -1178,7 +1195,7 @@ async def post_review_comment(
11781195
}
11791196
if labels:
11801197
payload["labels"] = labels
1181-
1198+
11821199
args = _create_post_args(url, payload)
11831200

11841201
try:

tests/unit/test_gerrit_config.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,5 +95,70 @@ def test_normalize_gerrit_url_with_mixed_mappings(self, mock_load_config):
9595
)
9696

9797

98+
@patch("gerrit_mcp_server.main.load_gerrit_config")
99+
def test_normalize_gerrit_url_adds_a_prefix_for_http_basic(self, mock_load_config):
100+
mock_load_config.return_value = {
101+
"gerrit_hosts": [
102+
{
103+
"name": "GerritHub",
104+
"external_url": "https://review.gerrithub.io/",
105+
"authentication": {"type": "http_basic", "username": "u", "auth_token": "t"},
106+
}
107+
]
108+
}
109+
gerrit_hosts = mock_load_config.return_value["gerrit_hosts"]
110+
self.assertEqual(
111+
_normalize_gerrit_url("review.gerrithub.io", gerrit_hosts), "https://review.gerrithub.io/a"
112+
)
113+
114+
@patch("gerrit_mcp_server.main.load_gerrit_config")
115+
def test_normalize_gerrit_url_adds_a_prefix_for_git_cookies(self, mock_load_config):
116+
mock_load_config.return_value = {
117+
"gerrit_hosts": [
118+
{
119+
"name": "Self-hosted",
120+
"external_url": "https://gerrit.example.com/",
121+
"authentication": {"type": "git_cookies", "gitcookies_path": "~/.gitcookies"},
122+
}
123+
]
124+
}
125+
gerrit_hosts = mock_load_config.return_value["gerrit_hosts"]
126+
self.assertEqual(
127+
_normalize_gerrit_url("gerrit.example.com", gerrit_hosts), "https://gerrit.example.com/a"
128+
)
129+
130+
@patch("gerrit_mcp_server.main.load_gerrit_config")
131+
def test_normalize_gerrit_url_no_a_prefix_for_gob_curl(self, mock_load_config):
132+
mock_load_config.return_value = {
133+
"gerrit_hosts": [
134+
{
135+
"name": "Google",
136+
"external_url": "https://fuchsia-review.googlesource.com/",
137+
"authentication": {"type": "gob_curl"},
138+
}
139+
]
140+
}
141+
gerrit_hosts = mock_load_config.return_value["gerrit_hosts"]
142+
self.assertEqual(
143+
_normalize_gerrit_url("fuchsia-review.googlesource.com", gerrit_hosts),
144+
"https://fuchsia-review.googlesource.com",
145+
)
146+
147+
@patch("gerrit_mcp_server.main.load_gerrit_config")
148+
def test_normalize_gerrit_url_no_a_prefix_without_authentication(self, mock_load_config):
149+
mock_load_config.return_value = {
150+
"gerrit_hosts": [
151+
{
152+
"name": "NoAuth",
153+
"external_url": "https://noauth.gerrit.com/",
154+
}
155+
]
156+
}
157+
gerrit_hosts = mock_load_config.return_value["gerrit_hosts"]
158+
self.assertEqual(
159+
_normalize_gerrit_url("noauth.gerrit.com", gerrit_hosts), "https://noauth.gerrit.com"
160+
)
161+
162+
98163
if __name__ == "__main__":
99164
unittest.main()

tests/unit/test_gerrit_urls.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,26 @@ def test_dispatches_to_gitcookies(self, mock_get_auth):
8181
mock_get_auth.assert_called_once_with(url, auth_config)
8282
self.assertEqual(command, ["curl", "-b", "cookie", "-L"])
8383

84+
@patch("gerrit_mcp_server.gerrit_auth._get_auth_for_http_basic")
85+
def test_dispatches_correctly_with_a_prefix_in_url(self, mock_get_auth):
86+
"""Tests that the dispatcher matches the host when the URL contains /a suffix."""
87+
auth_config = {"type": "http_basic", "username": "test", "auth_token": "token"}
88+
mock_get_auth.return_value = ["curl", "--user", "test:token", "-L"]
89+
config = {
90+
"gerrit_hosts": [
91+
{
92+
"name": "GerritHub",
93+
"external_url": "https://review.gerrithub.io/",
94+
"authentication": auth_config,
95+
}
96+
]
97+
}
98+
command = gerrit_urls.get_curl_command_for_gerrit_url(
99+
"https://review.gerrithub.io/a", config
100+
)
101+
mock_get_auth.assert_called_once_with(auth_config)
102+
self.assertEqual(command, ["curl", "--user", "test:token", "-L"])
103+
84104
def test_no_matching_host_raises_error(self):
85105
"""Tests that a ValueError is raised when no matching host is found."""
86106
config = {"gerrit_hosts": []}

0 commit comments

Comments
 (0)