Skip to content

Commit c62d76b

Browse files
fix: quote project names in JQL queries to handle reserved keywords (sooperset#553)
Quote project names in JQL queries to prevent syntax errors when project keys match JQL reserved keywords (e.g., "AND", "OR", "IN"). Previously, queries like `project = AND` would fail with a JQL parsing error. Now properly quoted as `project = "AND"`. Changes: - Quote project keys in get_project_issues_count() and get_project_issues() - Quote single project in search_issues() filter - Add test case for reserved keyword project names - Update all affected test assertions This ensures JQL queries work correctly regardless of project naming. Reported-by: Amichai Schreiber Github-Issue: sooperset#553
1 parent ff4f19a commit c62d76b

File tree

4 files changed

+25
-11
lines changed

4 files changed

+25
-11
lines changed

src/mcp_atlassian/jira/projects.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ def get_project_issues_count(self, project_key: str) -> int:
283283
"""
284284
try:
285285
# Use JQL to count issues in the project
286-
jql = f"project = {project_key}"
286+
jql = f'project = "{project_key}"'
287287
result = self.jira.jql(jql=jql, fields="key", limit=1)
288288
if not isinstance(result, dict):
289289
msg = f"Unexpected return value type from `jira.jql`: {type(result)}"
@@ -319,7 +319,7 @@ def get_project_issues(
319319
"""
320320
try:
321321
# Use JQL to get issues in the project
322-
jql = f"project = {project_key}"
322+
jql = f'project = "{project_key}"'
323323

324324
return self.search_issues(jql, start=start, limit=limit)
325325

src/mcp_atlassian/jira/search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ def search_issues(
5757

5858
# Build the project filter query part
5959
if len(projects) == 1:
60-
project_query = f"project = {projects[0]}"
60+
project_query = f'project = "{projects[0]}"'
6161
else:
6262
quoted_projects = [f'"{p}"' for p in projects]
6363
projects_list = ", ".join(quoted_projects)

tests/unit/jira/test_projects.py

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ def test_get_project_issues(projects_mixin: ProjectsMixin):
166166

167167
# Verify search_issues was called, not jira.jql
168168
projects_mixin.search_issues.assert_called_once_with(
169-
"project = TEST",
169+
'project = "TEST"',
170170
start=0,
171171
limit=50,
172172
)
@@ -201,7 +201,7 @@ def test_get_project_issues_with_start(projects_mixin: ProjectsMixin) -> None:
201201

202202
# Verify search_issues was called with the correct arguments
203203
projects_mixin.search_issues.assert_called_once_with(
204-
f"project = {project_key}",
204+
f'project = "{project_key}"',
205205
start=start_index,
206206
limit=5,
207207
)
@@ -468,7 +468,21 @@ def test_get_project_issues_count(projects_mixin: ProjectsMixin):
468468
result = projects_mixin.get_project_issues_count("PROJ1")
469469
assert result == 42
470470
projects_mixin.jira.jql.assert_called_once_with(
471-
jql="project = PROJ1", fields="key", limit=1
471+
jql='project = "PROJ1"', fields="key", limit=1
472+
)
473+
474+
475+
def test_get_project_issues_count__project_with_reserved_keyword(
476+
projects_mixin: ProjectsMixin,
477+
):
478+
"""Test get_project_issues_count method."""
479+
jql_result = {"total": 42}
480+
projects_mixin.jira.jql.return_value = jql_result
481+
482+
result = projects_mixin.get_project_issues_count("AND")
483+
assert result == 42
484+
projects_mixin.jira.jql.assert_called_once_with(
485+
jql='project = "AND"', fields="key", limit=1
472486
)
473487

474488

@@ -508,7 +522,7 @@ def test_get_project_issues_with_search_mixin(projects_mixin: ProjectsMixin):
508522
result = projects_mixin.get_project_issues("PROJ1", start=10, limit=20)
509523
assert result == mock_search_result
510524
projects_mixin.search_issues.assert_called_once_with(
511-
"project = PROJ1", start=10, limit=20
525+
'project = "PROJ1"', start=10, limit=20
512526
)
513527
projects_mixin.jira.jql.assert_not_called()
514528

tests/unit/jira/test_search.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -291,7 +291,7 @@ def test_search_issues_with_projects_filter(self, search_mixin: SearchMixin):
291291
# Test with single project filter
292292
result = search_mixin.search_issues("text ~ 'test'", projects_filter="TEST")
293293
search_mixin.jira.jql.assert_called_with(
294-
"(text ~ 'test') AND project = TEST",
294+
"(text ~ 'test') AND project = \"TEST\"",
295295
fields=ANY,
296296
start=0,
297297
limit=50,
@@ -350,7 +350,7 @@ def test_search_issues_with_config_projects_filter(self, search_mixin: SearchMix
350350
# Test with override
351351
result = search_mixin.search_issues("text ~ 'test'", projects_filter="OVERRIDE")
352352
search_mixin.jira.jql.assert_called_with(
353-
"(text ~ 'test') AND project = OVERRIDE",
353+
"(text ~ 'test') AND project = \"OVERRIDE\"",
354354
fields=ANY,
355355
start=0,
356356
limit=50,
@@ -604,7 +604,7 @@ def test_search_issues_with_projects_filter_jql_construction(
604604

605605
# Assert: JQL verification
606606
api_method_mock.assert_called_with(
607-
"(text ~ 'test') AND project = TEST", # Check constructed JQL
607+
"(text ~ 'test') AND project = \"TEST\"", # Check constructed JQL
608608
**expected_kwargs,
609609
)
610610

@@ -670,5 +670,5 @@ def test_search_issues_with_config_projects_filter_jql_construction(
670670
search_mixin.search_issues("text ~ 'test'", projects_filter="OVERRIDE")
671671
# Assert: JQL verification
672672
api_method_mock.assert_called_with(
673-
"(text ~ 'test') AND project = OVERRIDE", **expected_kwargs
673+
"(text ~ 'test') AND project = \"OVERRIDE\"", **expected_kwargs
674674
)

0 commit comments

Comments
 (0)