Skip to content

Commit a56f1ac

Browse files
committed
🧪 test(maniphest): Enhance task search test coverage
Implement comprehensive test cases for task_search method: - Add mocking for Phabricator API interactions - Cover scenarios with text query, tag filtering - Test date-based filtering - Validate wildcard and complex tag logic - Remove placeholder tests with actual implementation Improves test reliability and ensures correct search behavior across different input combinations.
1 parent c41e854 commit a56f1ac

File tree

2 files changed

+219
-30
lines changed

2 files changed

+219
-30
lines changed

‎phabfive/maniphest.py‎

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,7 +1712,6 @@ def task_search(
17121712
log.info("No tag specified, searching across all projects")
17131713
constraints = {}
17141714

1715-
# Add free-text search if provided
17161715
if text_query:
17171716
log.info(f"Free-text search: '{text_query}'")
17181717
# Note: maniphest.search doesn't have a fullText constraint
@@ -1761,7 +1760,6 @@ def task_search(
17611760
for phid in project_phids:
17621761
constraints = {"projects": [phid]}
17631762

1764-
# Add free-text search if provided
17651763
if text_query:
17661764
constraints["query"] = text_query
17671765

@@ -1807,7 +1805,6 @@ def task_search(
18071805
# Single project
18081806
constraints = {"projects": project_phids}
18091807

1810-
# Add free-text search if provided
18111808
if text_query:
18121809
constraints["query"] = text_query
18131810

‎tests/test_maniphest.py‎

Lines changed: 219 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1058,44 +1058,236 @@ def test_task_search_yaml_output_is_parsable(self, mock_init, capsys):
10581058

10591059

10601060
class TestTaskSearchTextQuery:
1061-
"""Test suite for free-text search functionality (#107)."""
1061+
"""Test suite for free-text search functionality."""
10621062

1063-
def test_task_search_with_text_query_only(self):
1063+
@patch("phabfive.maniphest.Phabfive.__init__")
1064+
def test_task_search_with_text_query_only(self, mock_init, capsys):
10641065
"""Test searching with only a free-text query."""
1065-
# This test documents expected behavior for the new text search feature
1066-
# It would need a mock Phabricator instance to run
1067-
# Expected: API called with fullText constraint, no projects constraint
1068-
pass
1066+
mock_init.return_value = None
1067+
maniphest = Maniphest()
1068+
maniphest.phab = MagicMock()
1069+
maniphest.url = "https://phabricator.example.com"
1070+
1071+
# Mock the API response
1072+
mock_response = MagicMock()
1073+
mock_response.response = {"data": []}
1074+
mock_response.get.return_value = {"after": None}
1075+
maniphest.phab.maniphest.search.return_value = mock_response
1076+
1077+
# Mock project.query for board name resolution
1078+
maniphest.phab.project.query.return_value = {"data": {}}
1079+
1080+
# Call with text query only
1081+
maniphest.task_search(text_query="authentication bug")
1082+
1083+
# Verify API was called with query constraint only
1084+
assert maniphest.phab.maniphest.search.called
1085+
call_kwargs = maniphest.phab.maniphest.search.call_args[1]
1086+
constraints = call_kwargs["constraints"]
1087+
1088+
# Should have query constraint
1089+
assert "query" in constraints
1090+
assert constraints["query"] == "authentication bug"
1091+
1092+
# Should NOT have projects constraint
1093+
assert "projects" not in constraints
10691094

1070-
def test_task_search_with_tag_only(self):
1095+
@patch("phabfive.maniphest.Phabfive.__init__")
1096+
def test_task_search_with_tag_only(self, mock_init, capsys):
10711097
"""Test searching with only --tag option (backward compat)."""
1072-
# This test documents that --tag works for project filtering
1073-
# Expected: API called with projects constraint, no fullText
1074-
pass
1098+
mock_init.return_value = None
1099+
maniphest = Maniphest()
1100+
maniphest.phab = MagicMock()
1101+
maniphest.url = "https://phabricator.example.com"
1102+
1103+
# Mock project resolution
1104+
mock_project_result = MagicMock()
1105+
mock_project_result.get.return_value = {
1106+
"PHID-PROJ-123": {
1107+
"name": "MyProject",
1108+
"slugs": ["myproject"],
1109+
}
1110+
}
1111+
maniphest.phab.project.query.return_value = mock_project_result
10751112

1076-
def test_task_search_with_text_and_tag(self):
1113+
# Mock the API response
1114+
mock_response = MagicMock()
1115+
mock_response.response = {"data": []}
1116+
mock_response.get.return_value = {"after": None}
1117+
maniphest.phab.maniphest.search.return_value = mock_response
1118+
1119+
# Call with tag only
1120+
maniphest.task_search(tag="MyProject")
1121+
1122+
# Verify API was called with projects constraint only
1123+
assert maniphest.phab.maniphest.search.called
1124+
call_kwargs = maniphest.phab.maniphest.search.call_args[1]
1125+
constraints = call_kwargs["constraints"]
1126+
1127+
# Should have projects constraint
1128+
assert "projects" in constraints
1129+
assert constraints["projects"] == ["PHID-PROJ-123"]
1130+
1131+
# Should NOT have query constraint
1132+
assert "query" not in constraints
1133+
1134+
@patch("phabfive.maniphest.Phabfive.__init__")
1135+
def test_task_search_with_text_and_tag(self, mock_init, capsys):
10771136
"""Test searching with both text query and tag filter."""
1078-
# This test documents combined search behavior
1079-
# Expected: API called with both fullText and projects constraints
1080-
pass
1137+
mock_init.return_value = None
1138+
maniphest = Maniphest()
1139+
maniphest.phab = MagicMock()
1140+
maniphest.url = "https://phabricator.example.com"
10811141

1082-
def test_task_search_requires_at_least_one_filter(self):
1142+
# Mock project resolution
1143+
mock_project_result = MagicMock()
1144+
mock_project_result.get.return_value = {
1145+
"PHID-PROJ-123": {
1146+
"name": "MyProject",
1147+
"slugs": ["myproject"],
1148+
}
1149+
}
1150+
maniphest.phab.project.query.return_value = mock_project_result
1151+
1152+
# Mock the API response
1153+
mock_response = MagicMock()
1154+
mock_response.response = {"data": []}
1155+
mock_response.get.return_value = {"after": None}
1156+
maniphest.phab.maniphest.search.return_value = mock_response
1157+
1158+
# Call with both text query and tag
1159+
maniphest.task_search(text_query="bug", tag="MyProject")
1160+
1161+
# Verify API was called with both constraints
1162+
assert maniphest.phab.maniphest.search.called
1163+
call_kwargs = maniphest.phab.maniphest.search.call_args[1]
1164+
constraints = call_kwargs["constraints"]
1165+
1166+
# Should have both constraints
1167+
assert "query" in constraints
1168+
assert constraints["query"] == "bug"
1169+
assert "projects" in constraints
1170+
assert constraints["projects"] == ["PHID-PROJ-123"]
1171+
1172+
@patch("phabfive.maniphest.Phabfive.__init__")
1173+
def test_task_search_requires_at_least_one_filter(self, mock_init, capsys):
10831174
"""Test that search without any filters shows error."""
1084-
# This test documents validation behavior
1085-
# Expected: Error logged, no API call made
1086-
pass
1175+
mock_init.return_value = None
1176+
maniphest = Maniphest()
1177+
maniphest.phab = MagicMock()
1178+
1179+
# Call with no filters
1180+
maniphest.task_search()
1181+
1182+
# Verify error was logged (captured in stderr by capsys)
1183+
_ = capsys.readouterr()
10871184

1088-
def test_task_search_with_text_and_date_filters(self):
1185+
# Verify API was NOT called
1186+
assert not maniphest.phab.maniphest.search.called
1187+
1188+
@patch("phabfive.maniphest.Phabfive.__init__")
1189+
def test_task_search_with_text_and_date_filters(self, mock_init, capsys):
10891190
"""Test text search with date filters."""
1090-
# Expected: API called with fullText, createdStart, and/or modifiedStart
1091-
pass
1191+
mock_init.return_value = None
1192+
maniphest = Maniphest()
1193+
maniphest.phab = MagicMock()
1194+
maniphest.url = "https://phabricator.example.com"
1195+
1196+
# Mock the API response
1197+
mock_response = MagicMock()
1198+
mock_response.response = {"data": []}
1199+
mock_response.get.return_value = {"after": None}
1200+
maniphest.phab.maniphest.search.return_value = mock_response
10921201

1093-
def test_task_search_tag_supports_wildcards(self):
1202+
# Mock project.query for board name resolution
1203+
maniphest.phab.project.query.return_value = {"data": {}}
1204+
1205+
# Call with text query and date filters
1206+
maniphest.task_search(text_query="bug", created_after=7, updated_after=3)
1207+
1208+
# Verify API was called with all constraints
1209+
assert maniphest.phab.maniphest.search.called
1210+
call_kwargs = maniphest.phab.maniphest.search.call_args[1]
1211+
constraints = call_kwargs["constraints"]
1212+
1213+
# Should have query constraint
1214+
assert "query" in constraints
1215+
assert constraints["query"] == "bug"
1216+
1217+
# Should have date constraints (converted to Unix timestamps)
1218+
assert "createdStart" in constraints
1219+
assert "modifiedStart" in constraints
1220+
assert isinstance(constraints["createdStart"], int)
1221+
assert isinstance(constraints["modifiedStart"], int)
1222+
1223+
@patch("phabfive.maniphest.Phabfive.__init__")
1224+
def test_task_search_tag_supports_wildcards(self, mock_init, capsys):
10941225
"""Test that --tag option supports wildcard patterns."""
1095-
# Expected: Wildcard resolution works same as before
1096-
pass
1226+
mock_init.return_value = None
1227+
maniphest = Maniphest()
1228+
maniphest.phab = MagicMock()
1229+
maniphest.url = "https://phabricator.example.com"
10971230

1098-
def test_task_search_tag_supports_and_or_logic(self):
1231+
# Mock project resolution with wildcard match
1232+
mock_project_result = MagicMock()
1233+
mock_project_result.get.return_value = {
1234+
"PHID-PROJ-1": {
1235+
"name": "Development",
1236+
"slugs": ["development", "dev"],
1237+
},
1238+
"PHID-PROJ-2": {
1239+
"name": "DevOps",
1240+
"slugs": ["devops"],
1241+
},
1242+
}
1243+
maniphest.phab.project.query.return_value = mock_project_result
1244+
1245+
# Mock the API response
1246+
mock_response = MagicMock()
1247+
mock_response.response = {"data": []}
1248+
mock_response.get.return_value = {"after": None}
1249+
maniphest.phab.maniphest.search.return_value = mock_response
1250+
1251+
# Call with wildcard pattern
1252+
maniphest.task_search(tag="dev*")
1253+
1254+
# Verify API was called multiple times (once per matched project)
1255+
assert maniphest.phab.maniphest.search.called
1256+
# With 2 matching projects, should make 2 calls
1257+
assert maniphest.phab.maniphest.search.call_count >= 2
1258+
1259+
@patch("phabfive.maniphest.Phabfive.__init__")
1260+
def test_task_search_tag_supports_and_or_logic(self, mock_init, capsys):
10991261
"""Test that --tag option supports AND/OR pattern logic."""
1100-
# Expected: Complex patterns like "ProjectA+ProjectB,ProjectC" work
1101-
pass
1262+
mock_init.return_value = None
1263+
maniphest = Maniphest()
1264+
maniphest.phab = MagicMock()
1265+
maniphest.url = "https://phabricator.example.com"
1266+
1267+
# Mock project resolution
1268+
mock_project_result = MagicMock()
1269+
mock_project_result.get.return_value = {
1270+
"PHID-PROJ-A": {
1271+
"name": "ProjectA",
1272+
"slugs": ["projecta"],
1273+
},
1274+
"PHID-PROJ-B": {
1275+
"name": "ProjectB",
1276+
"slugs": ["projectb"],
1277+
},
1278+
}
1279+
maniphest.phab.project.query.return_value = mock_project_result
1280+
1281+
# Mock the API response
1282+
mock_response = MagicMock()
1283+
mock_response.response = {"data": []}
1284+
mock_response.get.return_value = {"after": None}
1285+
maniphest.phab.maniphest.search.return_value = mock_response
1286+
1287+
# Test OR logic: ProjectA,ProjectB
1288+
maniphest.task_search(tag="ProjectA,ProjectB")
1289+
1290+
# Verify API was called (implementation fetches both projects)
1291+
assert maniphest.phab.maniphest.search.called
1292+
# Should make multiple calls for OR logic
1293+
assert maniphest.phab.maniphest.search.call_count >= 2

0 commit comments

Comments
 (0)