Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions jbi/bugzilla/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,11 +173,15 @@ def is_assigned(self) -> bool:
return self.assigned_to != "[email protected]"

def extract_from_see_also(self, project_key):
"""Extract Jira Issue Key from see_also if jira url present"""
"""Extract Jira Issue Key from see_also if jira url present for the specified project.

Returns the Jira issue key only if it matches the specified project_key.
If a see_also link points to a different Jira project, it will not be returned,
allowing a new ticket to be created for the specified project.
"""
if not self.see_also or len(self.see_also) == 0:
return None

candidates = []
for url in self.see_also:
try:
parsed_url: ParseResult = urlparse(url=url)
Expand All @@ -199,12 +203,11 @@ def extract_from_see_also(self, project_key):
parsed_jira_key = parsed_url.path.rstrip("/").split("/")[-1]
if parsed_jira_key: # URL ending with /
# Issue keys are like `{project_key}-{number}`
# Only return if the key matches the specified project
if parsed_jira_key.startswith(f"{project_key}-"):
return parsed_jira_key
# If not obvious, then keep this link as candidate.
candidates.append(parsed_jira_key)

return candidates[0] if candidates else None
return None


class WebhookRequest(BaseModel, frozen=True):
Expand Down
49 changes: 41 additions & 8 deletions tests/unit/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,34 +64,67 @@ def test_override_step_configuration_for_single_action_type():
([], None),
(["foo"], None),
(["fail:/format"], None),
(["foo", "http://jira.net/123"], "123"),
# Non-matching project keys should return None
(["foo", "http://jira.net/123"], None),
(["http://org/123"], None),
(["http://jira.com"], None),
(["http://mozilla.jira.com/"], None),
(["http://mozilla.jira.com/123"], "123"),
(["http://mozilla.jira.com/123/"], "123"),
(["http://mozilla.jira.com/ticket/123"], "123"),
(["http://atlassian.com/ticket/123"], "123"),
(["http://mozilla.jira.com/123", "http://mozilla.jira.com/456"], "123"),
(["http://mozilla.jira.com/123"], None),
(["http://mozilla.jira.com/123/"], None),
(["http://mozilla.jira.com/ticket/123"], None),
(["http://atlassian.com/ticket/123"], None),
(["http://mozilla.jira.com/123", "http://mozilla.jira.com/456"], None),
# Multiple Jira issues from different projects should return None if none match
(
["http://mozilla.jira.com/FOO-123", "http://mozilla.jira.com/BAR-456"],
"FOO-123",
None,
),
# Issue keys that don't match the project format should return None
(
["http://mozilla.jira.com/FOO-123", "http://mozilla.jira.com/JBI456"],
"FOO-123",
None,
),
# Only return issue key if it matches the specified project
(
["http://mozilla.jira.com/FOO-123", "http://mozilla.jira.com/JBI-456"],
"JBI-456",
),
# Test the specific scenario: BZFFX issue shouldn't prevent GENAI creation
(
["http://mozilla.jira.com/BZFFX-123"],
None,
),
],
)
def test_extract_see_also(see_also, expected, bug_factory):
bug = bug_factory(see_also=see_also)
assert bug.extract_from_see_also("JBI") == expected


def test_extract_see_also_different_projects(bug_factory):
"""Test that a bug with a BZFFX issue can still match GENAI project."""
bug = bug_factory(see_also=["http://mozilla.jira.com/browse/BZFFX-123"])
# When looking for GENAI project, should return None (allowing new ticket creation)
assert bug.extract_from_see_also("GENAI") is None
# When looking for BZFFX project, should return the issue key
assert bug.extract_from_see_also("BZFFX") == "BZFFX-123"


def test_extract_see_also_multiple_projects(bug_factory):
"""Test that extract_from_see_also correctly handles bugs linked to multiple projects."""
bug = bug_factory(
see_also=[
"http://mozilla.jira.com/browse/BZFFX-123",
"http://mozilla.jira.com/browse/GENAI-456",
]
)
# Each project should only match its own issue
assert bug.extract_from_see_also("BZFFX") == "BZFFX-123"
assert bug.extract_from_see_also("GENAI") == "GENAI-456"
# Non-matching project should return None
assert bug.extract_from_see_also("FOOBAR") is None


@pytest.mark.parametrize(
"product,component,expected",
[
Expand Down
4 changes: 3 additions & 1 deletion tests/unit/test_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,9 @@ def test_runner_ignores_request_if_jira_is_linked_but_without_whiteboard(
)
mocked_bugzilla.get_bug.return_value = webhook.bug

assert webhook.bug.extract_from_see_also(project_key="foo") is not None
# Verify that the bug has a JBI link (matching project), but not for other projects
assert webhook.bug.extract_from_see_also(project_key="JBI") == "JBI-234"
assert webhook.bug.extract_from_see_also(project_key="foo") is None

with pytest.raises(IgnoreInvalidRequestError) as exc_info:
execute_action(request=webhook, actions=actions)
Expand Down
6 changes: 2 additions & 4 deletions tests/unit/test_steps.py
Original file line number Diff line number Diff line change
Expand Up @@ -1378,8 +1378,7 @@ def test_maybe_update_components_create_components_normal_component(
)

mocked_jira.create_component.assert_called_once_with(
project=action_context.jira.project,
name="NewComponent",
{"project": action_context.jira.project, "name": "NewComponent"}
)
mocked_jira.update_issue_field.assert_called_with(
key="JBI-234",
Expand Down Expand Up @@ -1419,8 +1418,7 @@ def test_maybe_update_components_create_components_prefix_component(
)

mocked_jira.create_component.assert_called_once_with(
project=action_context.jira.project,
name="Firefox::NewComponent",
{"project": action_context.jira.project, "name": "Firefox::NewComponent"}
)
mocked_jira.update_issue_field.assert_called_with(
key="JBI-234",
Expand Down
Loading