Skip to content

Commit 757b806

Browse files
Fix #475, #382: Fix lookup of actions when tag contains dash (#472)
* Updates to lookup_action logic-- instead of constructing tags from the whiteboard, then trying to see which tag exists as an action. In this commit we attempt to use the action.whiteboard_tag as the element which must exist within the bug.whiteboard element--iterating through the actions, and acting on the first action that applies to the bug. The whiteboard_tag presented in the action must be contained within the bug’s whiteboard field with a prefix of [ * Fix mypy * Fixing test: prefix of [ required * Resolve tests * Updating logs to reflect intent * minor update to search string * Defaulted behavior of pre/post fixed brackets as a requirement, is now toggleable * Changes to readme * Adjust factory-usage; make factory method available as fixture. Add tests to test_bugzilla * Fix regular expression and rewrite tests using parameterize * Remove brackets_required option * Remove redundant tests * Remove whiteboard manipulation with reverse lookup * Rename variables in tests * Rewrite test to match scope of PR changes --------- Co-authored-by: Mathieu Leplatre <[email protected]>
1 parent 1d37db8 commit 757b806

File tree

8 files changed

+118
-84
lines changed

8 files changed

+118
-84
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ graph TD
4848
- `make start`: run the application locally (http://localhost:8000)
4949
- `make test`: run the unit tests suites
5050
- `make lint`: static analysis of the code base
51+
- `make format`: automatically format code to align to linting standards
5152

5253
You may consider:
5354

jbi/models.py

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import functools
66
import importlib
77
import logging
8+
import re
89
import warnings
910
from inspect import signature
1011
from types import ModuleType
@@ -233,40 +234,21 @@ def is_assigned(self) -> bool:
233234

234235
def get_whiteboard_as_list(self) -> list[str]:
235236
"""Convert string whiteboard into list, splitting on ']' and removing '['."""
236-
if self.whiteboard is not None:
237-
split_list = self.whiteboard.replace("[", "").split("]")
238-
return [x.strip() for x in split_list if x not in ["", " "]]
239-
return []
240-
241-
def get_whiteboard_with_brackets_as_list(self) -> list[str]:
242-
"""Convert string whiteboard into list, splitting on ']' and removing '['; then re-adding."""
243-
wb_list = self.get_whiteboard_as_list()
244-
if wb_list is not None and len(wb_list) > 0:
245-
return [f"[{element}]" for element in wb_list]
246-
return []
237+
split_list = (
238+
self.whiteboard.replace("[", "").split("]") if self.whiteboard else []
239+
)
240+
return [x.strip() for x in split_list if x not in ["", " "]]
247241

248242
def get_jira_labels(self) -> list[str]:
249243
"""
250244
whiteboard labels are added as a convenience for users to search in jira;
251245
bugzilla is an expected label in Jira
252246
since jira-labels can't contain a " ", convert to "."
253247
"""
254-
wb_list = [wb.replace(" ", ".") for wb in self.get_whiteboard_as_list()]
255-
wb_bracket_list = [
256-
wb.replace(" ", ".") for wb in self.get_whiteboard_with_brackets_as_list()
257-
]
258-
259-
return ["bugzilla"] + wb_list + wb_bracket_list
260-
261-
def get_potential_whiteboard_config_list(self) -> list[str]:
262-
"""Get all possible tags from `whiteboard` field"""
263-
converted_list: list = []
264-
for whiteboard in self.get_whiteboard_as_list():
265-
first_tag = whiteboard.strip().lower().split(sep="-", maxsplit=1)[0]
266-
if first_tag:
267-
converted_list.append(first_tag)
268-
269-
return converted_list
248+
wb_list = self.get_whiteboard_as_list()
249+
wb_bracket_list = [f"[{wb}]" for wb in wb_list]
250+
without_spaces = [wb.replace(" ", ".") for wb in (wb_list + wb_bracket_list)]
251+
return ["bugzilla"] + without_spaces
270252

271253
def issue_type(self) -> str:
272254
"""Get the Jira issue type for this bug"""
@@ -304,11 +286,13 @@ def extract_from_see_also(self):
304286

305287
def lookup_action(self, actions: Actions) -> Action:
306288
"""Find first matching action from bug's whiteboard list"""
307-
tags: list[str] = self.get_potential_whiteboard_config_list()
308-
for tag in tags:
309-
if action := actions.get(tag):
310-
return action
311-
raise ActionNotFoundError(", ".join(tags))
289+
if self.whiteboard:
290+
for tag, action in actions.by_tag.items():
291+
search_string = r"\[[^\]]*" + tag + r"[^\]]*\]"
292+
if re.search(search_string, self.whiteboard, flags=re.IGNORECASE):
293+
return action
294+
295+
raise ActionNotFoundError(", ".join(actions.by_tag.keys()))
312296

313297

314298
class BugzillaWebhookRequest(BaseModel):

jbi/runner.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ def execute_action(
6464
action = bug.lookup_action(actions)
6565
except ActionNotFoundError as err:
6666
raise IgnoreInvalidRequestError(
67-
f"no action matching bug whiteboard tags: {err}"
67+
f"no bug whiteboard matching action tags: {err}"
6868
) from err
6969
runner_context = runner_context.update(action=action)
7070

tests/conftest.py

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,7 @@
2020
BugzillaWebhookRequest,
2121
)
2222
from jbi.services import bugzilla, jira
23-
from tests.fixtures.factories import (
24-
action_context_factory,
25-
action_factory,
26-
bug_factory,
27-
jira_context_factory,
28-
webhook_event_factory,
29-
webhook_factory,
30-
webhook_user_factory,
31-
)
23+
from tests.fixtures import factories
3224

3325

3426
@pytest.fixture(autouse=True)
@@ -85,25 +77,29 @@ def mocked_responses():
8577

8678
@pytest.fixture
8779
def context_create_example() -> ActionContext:
88-
return action_context_factory(
80+
return factories.action_context_factory(
8981
operation=Operation.CREATE,
9082
)
9183

9284

9385
@pytest.fixture
9486
def context_update_example() -> ActionContext:
95-
bug = bug_factory(see_also=["https://mozilla.atlassian.net/browse/JBI-234"])
96-
context = action_context_factory(
87+
bug = factories.bug_factory(
88+
see_also=["https://mozilla.atlassian.net/browse/JBI-234"]
89+
)
90+
context = factories.action_context_factory(
9791
operation=Operation.UPDATE,
9892
bug=bug,
99-
jira=jira_context_factory(issue=bug.extract_from_see_also()),
93+
jira=factories.jira_context_factory(issue=bug.extract_from_see_also()),
10094
)
10195
return context
10296

10397

10498
@pytest.fixture
10599
def context_update_status_assignee() -> ActionContext:
106-
bug = bug_factory(see_also=["https://mozilla.atlassian.net/browse/JBI-234"])
100+
bug = factories.bug_factory(
101+
see_also=["https://mozilla.atlassian.net/browse/JBI-234"]
102+
)
107103
changes = [
108104
{
109105
"field": "status",
@@ -116,84 +112,88 @@ def context_update_status_assignee() -> ActionContext:
116112
"added": "[email protected]",
117113
},
118114
]
119-
event = webhook_event_factory(routing_key="bug.modify", changes=changes)
120-
context = action_context_factory(
115+
event = factories.webhook_event_factory(routing_key="bug.modify", changes=changes)
116+
context = factories.action_context_factory(
121117
operation=Operation.UPDATE,
122118
bug=bug,
123119
event=event,
124-
jira=jira_context_factory(issue=bug.extract_from_see_also()),
120+
jira=factories.jira_context_factory(issue=bug.extract_from_see_also()),
125121
)
126122
return context
127123

128124

129125
@pytest.fixture
130126
def context_comment_example() -> ActionContext:
131-
user = webhook_user_factory(login="[email protected]")
127+
user = factories.webhook_user_factory(login="[email protected]")
132128
comment = BugzillaWebhookComment.parse_obj({"number": 2, "body": "hello"})
133-
bug = bug_factory(
129+
bug = factories.bug_factory(
134130
see_also=["https://mozilla.atlassian.net/browse/JBI-234"],
135131
comment=comment,
136132
)
137-
event = webhook_event_factory(target="comment", user=user)
138-
context = action_context_factory(
133+
event = factories.webhook_event_factory(target="comment", user=user)
134+
context = factories.action_context_factory(
139135
operation=Operation.COMMENT,
140136
bug=bug,
141137
event=event,
142-
jira=jira_context_factory(issue=bug.extract_from_see_also()),
138+
jira=factories.jira_context_factory(issue=bug.extract_from_see_also()),
143139
)
144140
return context
145141

146142

147143
@pytest.fixture
148144
def context_update_resolution_example() -> ActionContext:
149-
bug = bug_factory(see_also=["https://mozilla.atlassian.net/browse/JBI-234"])
150-
event = webhook_event_factory(action="modify", routing_key="bug.modify:resolution")
151-
context = action_context_factory(
145+
bug = factories.bug_factory(
146+
see_also=["https://mozilla.atlassian.net/browse/JBI-234"]
147+
)
148+
event = factories.webhook_event_factory(
149+
action="modify", routing_key="bug.modify:resolution"
150+
)
151+
context = factories.action_context_factory(
152152
operation=Operation.UPDATE,
153153
bug=bug,
154154
event=event,
155-
jira=jira_context_factory(issue=bug.extract_from_see_also()),
155+
jira=factories.jira_context_factory(issue=bug.extract_from_see_also()),
156156
)
157157
return context
158158

159159

160160
@pytest.fixture
161161
def webhook_create_example() -> BugzillaWebhookRequest:
162-
webhook_payload = webhook_factory()
162+
webhook_payload = factories.webhook_factory()
163163

164164
return webhook_payload
165165

166166

167167
@pytest.fixture
168168
def webhook_comment_example() -> BugzillaWebhookRequest:
169-
user = webhook_user_factory(login="[email protected]")
169+
user = factories.webhook_user_factory(login="[email protected]")
170170
comment = BugzillaWebhookComment.parse_obj({"number": 2, "body": "hello"})
171-
bug = bug_factory(
171+
bug = factories.bug_factory(
172172
see_also=["https://mozilla.atlassian.net/browse/JBI-234"],
173173
comment=comment,
174174
)
175-
event = webhook_event_factory(target="comment", user=user)
176-
webhook_payload = webhook_factory(bug=bug, event=event)
175+
event = factories.webhook_event_factory(target="comment", user=user)
176+
webhook_payload = factories.webhook_factory(bug=bug, event=event)
177177

178178
return webhook_payload
179179

180180

181181
@pytest.fixture
182182
def webhook_private_comment_example() -> BugzillaWebhookRequest:
183-
user = webhook_user_factory(login="[email protected]")
184-
event = webhook_event_factory(target="comment", user=user)
185-
bug = bug_factory(
183+
user = factories.webhook_user_factory(login="[email protected]")
184+
event = factories.webhook_event_factory(target="comment", user=user)
185+
bug = factories.bug_factory(
186186
comment={"id": 344, "number": 2, "is_private": True},
187187
see_also=["https://mozilla.atlassian.net/browse/JBI-234"],
188188
)
189-
webhook_payload = webhook_factory(bug=bug, event=event)
189+
webhook_payload = factories.webhook_factory(bug=bug, event=event)
190190
return webhook_payload
191191

192192

193193
@pytest.fixture
194194
def webhook_create_private_example() -> BugzillaWebhookRequest:
195-
return webhook_factory(
196-
event=webhook_event_factory(),
195+
return factories.webhook_factory(
196+
event=factories.webhook_event_factory(),
197197
bug={"id": 654321, "is_private": True},
198198
)
199199

@@ -212,23 +212,30 @@ def webhook_change_status_assignee():
212212
"added": "[email protected]",
213213
},
214214
]
215-
event = webhook_event_factory(routing_key="bug.modify", changes=changes)
216-
webhook_payload = webhook_factory(event=event)
215+
event = factories.webhook_event_factory(routing_key="bug.modify", changes=changes)
216+
webhook_payload = factories.webhook_factory(event=event)
217217
return webhook_payload
218218

219219

220220
@pytest.fixture
221221
def webhook_modify_private_example() -> BugzillaWebhookRequest:
222-
event = webhook_event_factory(action="modify", routing_key="bug.modify:status")
223-
webhook_payload = webhook_factory(
222+
event = factories.webhook_event_factory(
223+
action="modify", routing_key="bug.modify:status"
224+
)
225+
webhook_payload = factories.webhook_factory(
224226
bug={"id": 654321, "is_private": True}, event=event
225227
)
226228
return webhook_payload
227229

228230

231+
@pytest.fixture
232+
def action_factory() -> Action:
233+
return factories.action_factory
234+
235+
229236
@pytest.fixture
230237
def action_example() -> Action:
231-
return action_factory()
238+
return factories.action_factory()
232239

233240

234241
@pytest.fixture

tests/fixtures/factories.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ def bug_factory(**overrides):
4545
"status": "NEW",
4646
"summary": "JBI Test",
4747
"type": "defect",
48-
"whiteboard": "devtest",
48+
"whiteboard": "[devtest]",
4949
**overrides,
5050
}
5151
return BugzillaBug.parse_obj(bug)

tests/unit/test_bugzilla.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import pytest
22

33
from jbi.errors import ActionNotFoundError
4+
from jbi.models import Actions
45
from tests.fixtures.factories import bug_factory
56

67

@@ -53,18 +54,34 @@ def test_extract_see_also(see_also, expected):
5354
assert bug.extract_from_see_also() == expected
5455

5556

56-
def test_lookup_action(actions_example):
57-
bug = bug_factory(id=1234, whiteboard="[example][DevTest]")
57+
@pytest.mark.parametrize(
58+
"whiteboard",
59+
[
60+
"[example][DevTest]",
61+
"[example][DevTest-test]",
62+
"[example][test-DevTest]",
63+
"[example][foo-DevTest-bar]",
64+
],
65+
)
66+
def test_lookup_action_found(whiteboard, actions_example):
67+
bug = bug_factory(id=1234, whiteboard=whiteboard)
5868
action = bug.lookup_action(actions_example)
5969
assert action.whiteboard_tag == "devtest"
6070
assert "test config" in action.description
6171

6272

63-
def test_lookup_action_missing(actions_example):
64-
bug = bug_factory(id=1234, whiteboard="example DevTest")
73+
@pytest.mark.parametrize(
74+
"whiteboard",
75+
[
76+
"example DevTest",
77+
"[foo] devtest [bar]",
78+
],
79+
)
80+
def test_lookup_action_not_found(whiteboard, actions_example):
81+
bug = bug_factory(id=1234, whiteboard=whiteboard)
6582
with pytest.raises(ActionNotFoundError) as exc_info:
6683
bug.lookup_action(actions_example)
67-
assert str(exc_info.value) == "example devtest"
84+
assert str(exc_info.value) == "devtest"
6885

6986

7087
def test_payload_empty_changes_list(webhook_change_status_assignee):

tests/unit/test_router.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def test_webhook_is_200_if_action_raises_IgnoreInvalidRequestError(
125125
assert response.status_code == 200
126126
assert (
127127
response.json()["error"]
128-
== "no action matching bug whiteboard tags: unmatched"
128+
== "no bug whiteboard matching action tags: devtest"
129129
)
130130

131131

0 commit comments

Comments
 (0)