Skip to content

Commit 9a38808

Browse files
author
Gaetano Guerriero
committed
[DEV-5150] Implement list_team_members controller
1 parent f30aaad commit 9a38808

File tree

3 files changed

+239
-0
lines changed

3 files changed

+239
-0
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from typing import Optional
2+
3+
from aiohttp import web
4+
5+
from athenian.api.internal.account import get_metadata_account_ids
6+
from athenian.api.internal.team import (
7+
fetch_team_members_recursively,
8+
get_all_team_members,
9+
get_root_team,
10+
get_team_from_db,
11+
)
12+
from athenian.api.models.state.models import Team
13+
from athenian.api.models.web import BadRequestError, Contributor
14+
from athenian.api.request import AthenianWebRequest
15+
from athenian.api.response import ResponseError, model_response
16+
17+
18+
async def list_team_members(
19+
request: AthenianWebRequest,
20+
team_id: int,
21+
recursive: bool,
22+
account: Optional[int] = None,
23+
) -> web.Response:
24+
"""List the members of a team."""
25+
sdb, mdb, cache = request.sdb, request.mdb, request.cache
26+
27+
if team_id == 0:
28+
if account is None:
29+
msg = "Parameter account is required with team_id 0"
30+
raise ResponseError(BadRequestError(detail=msg))
31+
team = await get_root_team(account, sdb)
32+
else:
33+
team = await get_team_from_db(team_id, account, request.uid, sdb)
34+
account = team[Team.owner_id.name]
35+
36+
meta_ids = await get_metadata_account_ids(account, sdb, cache)
37+
38+
if recursive:
39+
member_ids = await fetch_team_members_recursively(account, sdb, team[Team.id.name])
40+
else:
41+
member_ids = team[Team.members.name]
42+
members = await get_all_team_members(member_ids, account, meta_ids, mdb, sdb, cache)
43+
44+
def sort_key(member: Contributor) -> tuple[bool, str, str]:
45+
# first users with a name
46+
return (member.name is None, (member.name or "").lower(), member.login.lower())
47+
48+
return model_response(sorted(members.values(), key=sort_key))

server/tests/controllers/align/__init__.py

Whitespace-only changes.
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
from typing import Optional
2+
3+
from athenian.api.db import Database
4+
from tests.testutils.auth import force_request_auth
5+
from tests.testutils.db import DBCleaner, models_insert
6+
from tests.testutils.factory import metadata as md_factory
7+
from tests.testutils.factory.state import (
8+
MappedJIRAIdentityFactory,
9+
TeamFactory,
10+
UserAccountFactory,
11+
)
12+
from tests.testutils.requester import Requester
13+
14+
15+
class BaseListTeamMembersTest(Requester):
16+
async def _request(
17+
self,
18+
team_id: int,
19+
assert_status: int = 200,
20+
recursive: Optional[bool] = None,
21+
account: Optional[int] = None,
22+
headers: Optional[dict] = None,
23+
user_id: Optional[str] = None,
24+
) -> list[dict] | dict:
25+
path = f"/private/align/members/{team_id}"
26+
params = {}
27+
if recursive is not None:
28+
params["recursive"] = str(recursive).lower()
29+
if account is not None:
30+
params["account"] = str(account)
31+
headers = self.headers if headers is None else (self.headers | headers)
32+
33+
with force_request_auth(user_id, headers) as headers:
34+
response = await self.client.request(
35+
method="GET", path=path, headers=headers, params=params,
36+
)
37+
38+
assert response.status == assert_status
39+
return await response.json()
40+
41+
42+
class TestListTeamMembersErrors(BaseListTeamMembersTest):
43+
async def test_invalid_auth_token(self, sdb: Database) -> None:
44+
await models_insert(sdb, TeamFactory(id=1, members=[1, 2]))
45+
headers = {"Authorization": "Bearer invalid"}
46+
res = await self._request(1, 401, headers=headers)
47+
assert isinstance(res, dict)
48+
assert res["type"] == "/errors/Unauthorized"
49+
50+
async def test_team_not_found(self) -> None:
51+
res = await self._request(999, 404)
52+
assert isinstance(res, dict)
53+
assert res["detail"] == "Team 999 not found or access denied"
54+
55+
async def test_team_not_found_recursive(self) -> None:
56+
res = await self._request(999, 404, recursive=True)
57+
assert isinstance(res, dict)
58+
assert res["detail"] == "Team 999 not found or access denied"
59+
60+
async def test_team_account_mismatch(self, sdb: Database) -> None:
61+
await models_insert(sdb, TeamFactory(id=10, owner_id=3))
62+
res = await self._request(10, 404)
63+
assert isinstance(res, dict)
64+
assert res["detail"] == "Team 10 not found or access denied"
65+
66+
async def test_team_correct_account_param_mismatch(self, sdb: Database) -> None:
67+
await models_insert(sdb, TeamFactory(id=10))
68+
res = await self._request(10, 404, account=3)
69+
assert isinstance(res, dict)
70+
assert res["detail"] == "Team 10 not found or access denied"
71+
72+
async def test_team_account_mismatch_non_default_user(self, sdb: Database) -> None:
73+
await models_insert(
74+
sdb, UserAccountFactory(user_id="my-user", account_id=2), TeamFactory(id=10),
75+
)
76+
await self._request(10, 404, user_id="my-user")
77+
78+
async def test_root_team_multiple_roots(self, sdb: Database) -> None:
79+
await models_insert(sdb, TeamFactory(), TeamFactory())
80+
res = await self._request(0, 500, account=1)
81+
assert isinstance(res, dict)
82+
assert res["title"] == "Multiple root teams"
83+
assert res["detail"] == "Account 1 has multiple root teams"
84+
85+
async def test_root_team_no_team_exists(self, sdb: Database) -> None:
86+
res = await self._request(0, 404, account=1)
87+
assert isinstance(res, dict)
88+
assert res["type"] == "/errors/teams/TeamNotFound"
89+
assert res["detail"] == "Root team not found or access denied"
90+
91+
async def test_root_team_missing_account(self, sdb: Database) -> None:
92+
res = await self._request(0, 400, account=None)
93+
assert isinstance(res, dict)
94+
assert res["type"] == "/errors/BadRequest"
95+
96+
97+
class TestListTeamMembers(BaseListTeamMembersTest):
98+
async def test_explicit_non_root_team(self, sdb: Database) -> None:
99+
# members id are from the 6 MB metadata db fixture
100+
await models_insert(
101+
sdb,
102+
TeamFactory(id=1, members=[]),
103+
TeamFactory(id=2, members=[40078, 39652900], parent_id=1),
104+
)
105+
106+
res = await self._request(2)
107+
assert [m["login"] for m in res] == ["github.com/leantrace", "github.com/reujab"]
108+
assert [m["name"] for m in res] == ["Alexander Schamne", "Christopher Knight"]
109+
110+
assert "email" in res[0]
111+
assert "picture" in res[0]
112+
113+
async def test_explicit_root_team(self, sdb: Database) -> None:
114+
await models_insert(sdb, TeamFactory(id=1, members=[40078], parent_id=None))
115+
res = await self._request(1)
116+
assert [m["login"] for m in res] == ["github.com/reujab"]
117+
118+
async def test_implicit_root_team_empty(self, sdb: Database) -> None:
119+
await models_insert(
120+
sdb,
121+
TeamFactory(id=10, members=[]),
122+
TeamFactory(id=20, parent_id=10, members=[3]),
123+
TeamFactory(id=30, parent_id=10, members=[3, 4]),
124+
)
125+
res = await self._request(0, account=1)
126+
assert res == []
127+
128+
async def test_implicit_root_team(self, sdb: Database) -> None:
129+
await models_insert(
130+
sdb,
131+
TeamFactory(id=10, members=[40078]),
132+
TeamFactory(id=11, parent_id=10, members=[39652900]),
133+
)
134+
res = await self._request(0, account=1)
135+
assert [m["login"] for m in res] == ["github.com/reujab"]
136+
137+
async def test_recursive(self, sdb: Database) -> None:
138+
await models_insert(
139+
sdb,
140+
TeamFactory(id=1, members=[39652900]),
141+
TeamFactory(id=2, parent_id=1, members=[40078]),
142+
)
143+
144+
res = await self._request(1, recursive=True)
145+
assert [m["login"] for m in res] == ["github.com/leantrace", "github.com/reujab"]
146+
147+
res = await self._request(1, recursive=False)
148+
assert [m["login"] for m in res] == ["github.com/leantrace"]
149+
150+
res = await self._request(1)
151+
assert [m["login"] for m in res] == ["github.com/leantrace"]
152+
153+
async def test_result_ordering(self, sdb: Database, mdb_rw: Database) -> None:
154+
await models_insert(
155+
sdb,
156+
TeamFactory(id=1, members=[100, 101, 102, 103]),
157+
)
158+
159+
async with DBCleaner(mdb_rw) as mdb_cleaner:
160+
models = [
161+
md_factory.UserFactory(node_id=100, html_url="https://c", name="Foo Bar"),
162+
md_factory.UserFactory(node_id=101, html_url="https://a", name=None),
163+
md_factory.UserFactory(node_id=102, html_url="https://b", name="zz-Top"),
164+
md_factory.UserFactory(node_id=103, html_url="https://d", name="aa"),
165+
]
166+
mdb_cleaner.add_models(*models)
167+
await models_insert(mdb_rw, *models)
168+
169+
res = await self._request(1, recursive=True)
170+
171+
assert [m["login"] for m in res] == ["d", "c", "b", "a"]
172+
assert [m.get("name") for m in res] == ["aa", "Foo Bar", "zz-Top", None]
173+
174+
async def test_jira_user_field(self, sdb: Database, mdb_rw: Database) -> None:
175+
await models_insert(
176+
sdb,
177+
TeamFactory(id=1, members=[100, 101]),
178+
MappedJIRAIdentityFactory(github_user_id=100, jira_user_id="200"),
179+
)
180+
181+
async with DBCleaner(mdb_rw) as mdb_cleaner:
182+
models = [
183+
md_factory.UserFactory(node_id=100),
184+
md_factory.UserFactory(node_id=101),
185+
md_factory.JIRAUserFactory(id="200", display_name="My JIRA name"),
186+
]
187+
mdb_cleaner.add_models(*models)
188+
await models_insert(mdb_rw, *models)
189+
res = await self._request(1)
190+
191+
assert [m.get("jira_user") for m in res] == ["My JIRA name", None]

0 commit comments

Comments
 (0)