Skip to content

Commit e68bf79

Browse files
authored
Jira Cloud user support (#1109)
* test_user allow_on_cloud, with appropriate fixes to jira.resources * update test_generic_resource * update search_allowed_users_for_issue() for Jira Cloud * add a search function to the Jira Cloud tests * add test_search_users() to allow_on_cloud
1 parent 11b87e2 commit e68bf79

File tree

5 files changed

+78
-60
lines changed

5 files changed

+78
-60
lines changed

jira/client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2897,7 +2897,11 @@ def user(self, id: str, expand: Optional[Any] = None) -> User:
28972897
Returns:
28982898
User
28992899
"""
2900-
user = User(self._options, self._session)
2900+
user = User(
2901+
self._options,
2902+
self._session,
2903+
_query_param="accountId" if self._is_cloud else "username",
2904+
)
29012905
params = {}
29022906
if expand is not None:
29032907
params["expand"] = expand
@@ -3152,7 +3156,7 @@ def search_allowed_users_for_issue(
31523156
Returns:
31533157
ResultList
31543158
"""
3155-
params = {"username": user}
3159+
params = {"query" if self._is_cloud else "username": user}
31563160
if issueKey is not None:
31573161
params["issueKey"] = issueKey
31583162
if projectKey is not None:

jira/resources.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1088,8 +1088,14 @@ def __init__(
10881088
options: Dict[str, str],
10891089
session: ResilientSession,
10901090
raw: Dict[str, Any] = None,
1091+
*,
1092+
_query_param: str = "username",
10911093
):
1092-
Resource.__init__(self, "user?username={0}", options, session)
1094+
# Handle self-hosted Jira and Jira Cloud differently
1095+
if raw and "accountId" in raw["self"]:
1096+
_query_param = "accountId"
1097+
1098+
Resource.__init__(self, f"user?{_query_param}" + "={0}", options, session)
10931099
if raw:
10941100
self._parse_raw(raw)
10951101
self.raw: Dict[str, Any] = cast(Dict[str, Any], self.raw)
@@ -1409,7 +1415,7 @@ def dict2resource(
14091415
r"securitylevel/[^/]+$": SecurityLevel,
14101416
r"status/[^/]+$": Status,
14111417
r"statuscategory/[^/]+$": StatusCategory,
1412-
r"user\?(username|key).+$": User,
1418+
r"user\?(username|key|accountId).+$": User,
14131419
r"group\?groupname.+$": Group,
14141420
r"version/[^/]+$": Version,
14151421
# GreenHopper specific resources

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,11 @@ def setUp(self) -> None:
7070
self.project_b = self.test_manager.project_b
7171
self.project_a = self.test_manager.project_a
7272

73+
@property
74+
def identifying_user_property(self) -> str:
75+
"""Literal["accountId", "name"]: Depending on if Jira Cloud or Server"""
76+
return "accountId" if self.is_jira_cloud_ci else "name"
77+
7378
@property
7479
def is_jira_cloud_ci(self) -> bool:
7580
"""is running on Jira Cloud"""
Lines changed: 33 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,38 @@
1-
import unittest
1+
import pytest
22

3-
from flaky import flaky
3+
import jira.resources
44

5-
from jira.resources import (
6-
Group,
7-
Issue,
8-
Project,
9-
Role,
10-
UnknownResource,
11-
cls_for_resource,
12-
)
5+
MOCK_URL = "http://customized-jira.com/rest/"
136

147

15-
@flaky
16-
class ResourceTests(unittest.TestCase):
17-
def setUp(self):
18-
pass
8+
def url_test_case(example_url: str):
9+
return f"{MOCK_URL}{example_url}"
1910

20-
def test_cls_for_resource(self):
21-
self.assertEqual(
22-
cls_for_resource(
23-
"https://jira.atlassian.com/rest/\
24-
api/latest/issue/JRA-1330"
25-
),
26-
Issue,
27-
)
28-
self.assertEqual(
29-
cls_for_resource(
30-
"http://localhost:2990/jira/rest/\
31-
api/latest/project/BULK"
32-
),
33-
Project,
34-
)
35-
self.assertEqual(
36-
cls_for_resource(
37-
"http://imaginary-jira.com/rest/\
38-
api/latest/project/IMG/role/10002"
39-
),
40-
Role,
41-
)
42-
self.assertEqual(
43-
cls_for_resource(
44-
"http://customized-jira.com/rest/\
45-
plugin-resource/4.5/json/getMyObject"
46-
),
47-
UnknownResource,
48-
)
49-
self.assertEqual(
50-
cls_for_resource(
51-
"http://customized-jira.com/rest/\
52-
group?groupname=bla"
53-
),
54-
Group,
55-
)
11+
12+
class TestResource:
13+
@pytest.mark.parametrize(
14+
["example_url", "expected_class"],
15+
# fmt: off
16+
[
17+
(url_test_case("api/latest/issue/JRA-1330"), jira.resources.Issue),
18+
(url_test_case("api/latest/project/BULK"), jira.resources.Project),
19+
(url_test_case("api/latest/project/IMG/role/10002"), jira.resources.Role),
20+
(url_test_case("plugin-resource/4.5/json/getMyObject"), jira.resources.UnknownResource),
21+
(url_test_case("group?groupname=bla"), jira.resources.Group),
22+
(url_test_case("user?username=bla"), jira.resources.User), # Jira Server / Data Center
23+
(url_test_case("user?accountId=bla"), jira.resources.User), # Jira Cloud
24+
],
25+
# fmt: on
26+
ids=[
27+
"issue",
28+
"project",
29+
"role",
30+
"unknown_resource",
31+
"group",
32+
"user",
33+
"user_cloud",
34+
],
35+
)
36+
def test_cls_for_resource(self, example_url, expected_class):
37+
"""Test the regex recognizes the right class for a given URL."""
38+
assert jira.resources.cls_for_resource(example_url) == expected_class

tests/resources/test_user.py

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,23 @@
11
import os
22

3-
from tests.conftest import TEST_ICON_PATH, JiraTestCase
3+
from jira.resources import User
4+
from tests.conftest import TEST_ICON_PATH, JiraTestCase, allow_on_cloud
45

56

67
class UserTests(JiraTestCase):
78
def setUp(self):
89
JiraTestCase.setUp(self)
910
self.issue = self.test_manager.project_b_issue3
1011

12+
@allow_on_cloud
1113
def test_user(self):
12-
user = self.jira.user(self.test_manager.user_admin.name)
13-
self.assertTrue(user.name)
14+
"""Test that a user can be returned and is the right class"""
15+
# GIVEN: a User
16+
expected_user = self.test_manager.user_admin
17+
# WHEN: The user is searched for using its identifying attribute
18+
user = self.jira.user(getattr(expected_user, self.identifying_user_property))
19+
# THEN: it is of the right type, and has an email address of the right format
20+
assert isinstance(user, User)
1421
self.assertRegex(
1522
user.emailAddress, r"^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$"
1623
)
@@ -148,11 +155,22 @@ def test_delete_user_avatar(self):
148155
)
149156
self.jira.delete_user_avatar(self.test_manager.CI_JIRA_ADMIN, props["id"])
150157

158+
@allow_on_cloud
151159
def test_search_users(self):
152-
users = self.jira.search_users(self.test_manager.CI_JIRA_ADMIN)
160+
# WHEN: the search_users function is called with a requested user
161+
if self.is_jira_cloud_ci:
162+
users = self.jira.search_users(query=self.test_manager.CI_JIRA_ADMIN)
163+
else:
164+
users = self.jira.search_users(self.test_manager.CI_JIRA_ADMIN)
165+
# THEN: We get a list of User objects
153166
self.assertGreaterEqual(len(users), 1)
154-
usernames = map(lambda user: user.name, users)
155-
self.assertIn(self.test_manager.user_admin.name, usernames)
167+
self.assertIsInstance(users[0], User)
168+
# and the requested user can be found in this list
169+
user_ids = [getattr(user, self.identifying_user_property) for user in users]
170+
self.assertIn(
171+
getattr(self.test_manager.user_admin, self.identifying_user_property),
172+
user_ids,
173+
)
156174

157175
def test_search_users_maxresults(self):
158176
users = self.jira.search_users(self.test_manager.CI_JIRA_USER, maxResults=1)
@@ -164,9 +182,11 @@ def test_search_allowed_users_for_issue_by_project(self):
164182
)
165183
self.assertGreaterEqual(len(users), 1)
166184

185+
@allow_on_cloud
167186
def test_search_allowed_users_for_issue_by_issue(self):
168187
users = self.jira.search_allowed_users_for_issue("a", issueKey=self.issue)
169188
self.assertGreaterEqual(len(users), 1)
189+
self.assertIsInstance(users[0], User)
170190

171191
def test_search_allowed_users_for_issue_maxresults(self):
172192
users = self.jira.search_allowed_users_for_issue(

0 commit comments

Comments
 (0)