Skip to content

Commit 49eb34c

Browse files
authored
Merge branch 'main' into konflux/references/main
2 parents eeaadf6 + 7d491a6 commit 49eb34c

File tree

6 files changed

+189
-42
lines changed

6 files changed

+189
-42
lines changed

Rover_Lookup/lookup.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,11 @@ def github_username_to_emails(
5454

5555
# Construct the LDAP filter to match the GitHub "Professional Social Media" URL.
5656
# The rhatSocialURL field contains values like "Github->https://github.com/username".
57+
# However, 5% of the entries include a trailing slash (which GitHub accepts),
58+
# so look for those, too.
5759
github_url = f"https://github.com/{github_username}"
58-
ldap_filter = f"(rhatSocialURL=Github->{github_url})"
60+
filter_clause = f"rhatSocialURL=Github->{github_url}"
61+
ldap_filter = f"(|({filter_clause})({filter_clause}/))"
5962

6063
# Attributes to retrieve (email fields)
6164
attributes = ["rhatPrimaryMail", "mail", "rhatPreferredAlias"]

Rover_Lookup/tests/test_lookup.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,9 @@ def test_successful_lookup_single_record(self, mock_connection_class, caplog):
7272
# Verify LDAP query was called correctly
7373
mock_conn.search.assert_called_once()
7474
search_args = mock_conn.search.call_args
75-
assert (
76-
search_args[1]["search_filter"]
77-
== "(rhatSocialURL=Github->https://github.com/test-user)"
78-
)
75+
filter_part = "rhatSocialURL=Github->https://github.com/test-user"
76+
expected_filter = f"(|({filter_part})({filter_part}/))"
77+
assert search_args[1]["search_filter"] == expected_filter
7978
assert "rhatPrimaryMail" in search_args[1]["attributes"]
8079
assert "mail" in search_args[1]["attributes"]
8180
assert "rhatPreferredAlias" in search_args[1]["attributes"]

sync2jira/upstream_issue.py

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -110,25 +110,25 @@
110110
"""
111111

112112

113-
def handle_github_message(body, config, is_pr=False):
113+
def passes_github_filters(item, config, upstream, item_type="issue"):
114114
"""
115-
Handle GitHub message from FedMsg.
116-
117-
:param Dict body: FedMsg Message body
118-
:param Dict config: Config File
119-
:param Bool is_pr: msg refers to a pull request
120-
:returns: Issue object
121-
:rtype: sync2jira.intermediary.Issue
115+
Apply GitHub filters (labels, milestone, other fields) to an item.
116+
117+
:param dict item: GitHub issue or PR data
118+
:param dict _filter: Filter configuration
119+
:param str upstream: Upstream repository name
120+
:param str item_type: Type of item for logging ("issue" or "PR")
121+
:returns: True if item passes all filters, False otherwise
122+
:rtype: bool
122123
"""
123-
owner = body["repository"]["owner"]["login"]
124-
repo = body["repository"]["name"]
125-
upstream = "{owner}/{repo}".format(owner=owner, repo=repo)
126-
124+
filter_config = (
125+
config["sync2jira"].get("filters", {}).get("github", {}).get(upstream, {})
126+
)
127127
mapped_repos = config["sync2jira"]["map"]["github"]
128128
if upstream not in mapped_repos:
129129
log.debug("%r not in Github map: %r", upstream, mapped_repos.keys())
130130
return None
131-
key = "pullrequest" if is_pr else "issue"
131+
key = "pullrequest" if item_type == "PR" else "issue"
132132
if key not in mapped_repos[upstream].get("sync", []):
133133
log.debug(
134134
"%r not in Github sync map: %r",
@@ -137,36 +137,57 @@ def handle_github_message(body, config, is_pr=False):
137137
)
138138
return None
139139

140-
_filter = config["sync2jira"].get("filters", {}).get("github", {}).get(upstream, {})
141-
142-
issue = body["issue"]
143-
for key, expected in _filter.items():
140+
for key, expected in filter_config.items():
144141
if key == "labels":
145-
# special handling for label: we look for it in the list of msg labels
146-
actual = {label["name"] for label in issue["labels"]}
142+
# special handling for label: we look for it in the list of labels
143+
actual = {label["name"] for label in item.get("labels", [])}
147144
if actual.isdisjoint(expected):
148-
log.debug("Labels %s not found on issue: %s", expected, upstream)
149-
return None
145+
log.debug(
146+
"Labels %s not found on %s: %s", expected, upstream, item_type
147+
)
148+
return False
150149
elif key == "milestone":
151150
# special handling for milestone: use the number
152-
milestone = issue.get(key) or {} # Key might exist with value `None`
151+
milestone = item.get(key) or {} # Key might exist with value `None`
153152
actual = milestone.get("number")
154153
if expected != actual:
155-
log.debug("Milestone %s not set on issue: %s", expected, upstream)
156-
return None
154+
log.debug(
155+
"Milestone %s not set on %s: %s", expected, upstream, item_type
156+
)
157+
return False
157158
else:
158159
# direct comparison
159-
actual = issue.get(key)
160+
actual = item.get(key)
160161
if actual != expected:
161162
log.debug(
162-
"Actual %r %r != expected %r on issue %s",
163+
"Actual %r %r != expected %r on %s %s",
163164
key,
164165
actual,
165166
expected,
166167
upstream,
168+
item_type,
167169
)
168-
return None
170+
return False
171+
return True
172+
173+
174+
def handle_github_message(body, config, is_pr=False):
175+
"""
176+
Handle GitHub message from FedMsg.
177+
178+
:param Dict body: FedMsg Message body
179+
:param Dict config: Config File
180+
:param Bool is_pr: msg refers to a pull request
181+
:returns: Issue object
182+
:rtype: sync2jira.intermediary.Issue
183+
"""
184+
owner = body["repository"]["owner"]["login"]
185+
repo = body["repository"]["name"]
186+
upstream = "{owner}/{repo}".format(owner=owner, repo=repo)
169187

188+
issue = body["issue"]
189+
if not passes_github_filters(issue, config, upstream, item_type="issue"):
190+
return None
170191
if is_pr and not issue.get("closed_at"):
171192
log.debug(
172193
"%r is a pull request. Ignoring.", issue.get("html_url", "<missing URL>")

sync2jira/upstream_pr.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,11 @@ def handle_github_message(body, config, suffix):
4141
repo = body["repository"]["name"]
4242
upstream = "{owner}/{repo}".format(owner=owner, repo=repo)
4343

44-
mapped_repos = config["sync2jira"]["map"]["github"]
45-
if upstream not in mapped_repos:
46-
log.debug("%r not in Github map: %r", upstream, mapped_repos.keys())
47-
return None
48-
elif "pullrequest" not in mapped_repos[upstream].get("sync", []):
49-
log.debug("%r not in Github PR map: %r", upstream, mapped_repos.keys())
50-
return None
51-
5244
pr = body["pull_request"]
53-
github_client = Github(config["sync2jira"]["github_token"])
45+
if not u_issue.passes_github_filters(pr, config, upstream, item_type="PR"):
46+
return None
47+
token = config["sync2jira"].get("github_token")
48+
github_client = Github(token, retry=5)
5449
reformat_github_pr(pr, upstream, github_client)
5550
return i.PR.from_github(upstream, pr, suffix, config)
5651

tests/test_upstream_issue.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -767,3 +767,92 @@ def test_add_project_values_early_exit(self, mock_requests_post):
767767
self.assertIsNone(result)
768768
# Reset mock
769769
mock_requests_post.reset_mock()
770+
771+
def test_passes_github_filters(self):
772+
"""
773+
Test passes_github_filters for labels, milestone, and other fields.
774+
Tests all filtering conditions in one test case.
775+
"""
776+
upstream = "org/repo"
777+
self.mock_config["sync2jira"]["filters"]["github"][upstream] = {
778+
"filter1": "filter1",
779+
"labels": ["custom_tag"],
780+
"milestone": 1,
781+
}
782+
783+
# Test 1: Bad label - should return False
784+
item = {
785+
"labels": [{"name": "bad_label"}],
786+
"milestone": {"number": 1},
787+
"filter1": "filter1",
788+
}
789+
self.assertFalse(
790+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
791+
)
792+
793+
# Test 2: Bad milestone - should return False
794+
item = {
795+
"labels": [{"name": "custom_tag"}],
796+
"milestone": {"number": 456},
797+
"filter1": "filter1",
798+
}
799+
self.assertFalse(
800+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
801+
)
802+
803+
# Test 3: Bad other field (filter1) - should return False
804+
item = {
805+
"labels": [{"name": "custom_tag"}],
806+
"milestone": {"number": 1},
807+
"filter1": "filter2",
808+
}
809+
self.assertFalse(
810+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
811+
)
812+
813+
# Test 4: All filters pass - should return True
814+
item = {
815+
"labels": [{"name": "custom_tag"}],
816+
"milestone": {"number": 1},
817+
"filter1": "filter1",
818+
}
819+
self.assertTrue(
820+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
821+
)
822+
823+
# Test 5: Config specifies only labels; item has matching label (wrong milestone/filter1 ignored) → True
824+
self.mock_config["sync2jira"]["filters"]["github"][upstream] = {
825+
"labels": ["custom_tag"]
826+
}
827+
item = {
828+
"labels": [{"name": "custom_tag"}],
829+
"milestone": {"number": 999},
830+
"filter1": "wrong",
831+
}
832+
self.assertTrue(
833+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
834+
)
835+
836+
# Test 6: Config specifies only milestone; item has matching milestone (wrong label/filter1 ignored) → True
837+
self.mock_config["sync2jira"]["filters"]["github"][upstream] = {"milestone": 1}
838+
item = {
839+
"labels": [{"name": "bad_label"}],
840+
"milestone": {"number": 1},
841+
"filter1": "wrong",
842+
}
843+
self.assertTrue(
844+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
845+
)
846+
847+
# Test 7: Config specifies only filter1; item has matching filter1 (wrong label/milestone ignored) → True
848+
self.mock_config["sync2jira"]["filters"]["github"][upstream] = {
849+
"filter1": "filter1"
850+
}
851+
item = {
852+
"labels": [{"name": "bad_label"}],
853+
"milestone": {"number": 999},
854+
"filter1": "filter1",
855+
}
856+
self.assertTrue(
857+
u.passes_github_filters(item, self.mock_config, upstream, item_type="issue")
858+
)

tests/test_upstream_pr.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def test_handle_github_message(self, mock_pr_from_github, mock_github):
123123
"mock_suffix",
124124
self.mock_config,
125125
)
126-
mock_github.assert_called_with("mock_token")
126+
mock_github.assert_called_with("mock_token", retry=5)
127127
self.assertEqual("Successful Call!", response)
128128
self.mock_github_client.get_repo.assert_called_with("org/repo")
129129
self.mock_github_repo.get_pull.assert_called_with(number="mock_number")
@@ -254,3 +254,43 @@ def test_filter_multiple_labels(
254254
self.mock_config["sync2jira"]["filters"]["github"]["org/repo"]["labels"],
255255
["custom_tag", "another_tag", "and_another"],
256256
)
257+
258+
@mock.patch("sync2jira.upstream_pr.u_issue.passes_github_filters")
259+
@mock.patch("sync2jira.intermediary.PR.from_github")
260+
def test_handle_github_message_filter_returns_false(
261+
self, mock_pr_from_github, mock_passes
262+
):
263+
"""When passes_github_filters returns False, handle_github_message returns None."""
264+
mock_passes.return_value = False
265+
266+
response = u.handle_github_message(
267+
body=self.mock_github_message_body,
268+
config=self.mock_config,
269+
suffix="mock_suffix",
270+
)
271+
272+
mock_passes.assert_called_once()
273+
mock_pr_from_github.assert_not_called()
274+
self.assertIsNone(response)
275+
276+
@mock.patch(PATH + "Github")
277+
@mock.patch("sync2jira.upstream_pr.u_issue.passes_github_filters")
278+
@mock.patch("sync2jira.intermediary.PR.from_github")
279+
def test_handle_github_message_filter_returns_true(
280+
self, mock_pr_from_github, mock_passes, mock_github
281+
):
282+
"""When passes_github_filters returns True, handle_github_message proceeds to PR.from_github."""
283+
mock_passes.return_value = True
284+
mock_pr_from_github.return_value = "Successful Call!"
285+
mock_github.return_value = self.mock_github_client
286+
287+
response = u.handle_github_message(
288+
body=self.mock_github_message_body,
289+
config=self.mock_config,
290+
suffix="mock_suffix",
291+
)
292+
293+
mock_passes.assert_called_once()
294+
mock_pr_from_github.assert_called_once()
295+
mock_github.assert_called_with("mock_token", retry=5)
296+
self.assertEqual("Successful Call!", response)

0 commit comments

Comments
 (0)