Skip to content

Commit 2381cf2

Browse files
authored
feat: TT-154 add is user member of a group endpoint (#259)
* feat: TT-154 add is user member of a group endpoint * fix: TT-154 modify name of variables in azure_users
1 parent eb11b24 commit 2381cf2

File tree

4 files changed

+132
-8
lines changed

4 files changed

+132
-8
lines changed

tests/time_tracker_api/users/users_namespace_test.py

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@
1010
@patch('utils.azure_users.AzureConnection.get_token', Mock())
1111
@patch('utils.azure_users.AzureConnection.get_user')
1212
def test_get_user_response_contains_expected_props(
13-
get_user_mock, client: FlaskClient, valid_header: dict,
13+
get_user_mock,
14+
client: FlaskClient,
15+
valid_header: dict,
1416
):
1517
get_user_mock.return_value = {
1618
'name': 'dummy',
@@ -35,7 +37,9 @@ def test_get_user_response_contains_expected_props(
3537
)
3638
@patch('utils.azure_users.AzureConnection.users')
3739
def test_users_response_contains_expected_props(
38-
users_mock, client: FlaskClient, valid_header: dict,
40+
users_mock,
41+
client: FlaskClient,
42+
valid_header: dict,
3943
):
4044
users_mock.return_value = [
4145
{'name': 'dummy', 'email': 'dummy', 'roles': ['dummy-role']}
@@ -54,7 +58,8 @@ def test_users_response_contains_expected_props(
5458
@patch('utils.azure_users.AzureConnection.get_token', Mock())
5559
@patch('utils.azure_users.AzureConnection.update_role')
5660
@mark.parametrize(
57-
'role_id,action', [('test', 'grant'), ('admin', 'revoke')],
61+
'role_id,action',
62+
[('test', 'grant'), ('admin', 'revoke')],
5863
)
5964
def test_update_role_response_contains_expected_props(
6065
update_role_mock,
@@ -70,7 +75,8 @@ def test_update_role_response_contains_expected_props(
7075
'roles': [],
7176
}
7277
response = client.post(
73-
f'/users/{user_id}/roles/{role_id}/{action}', headers=valid_header,
78+
f'/users/{user_id}/roles/{role_id}/{action}',
79+
headers=valid_header,
7480
)
7581
assert HTTPStatus.OK == response.status_code
7682
assert 'name' in json.loads(response.data)
@@ -101,10 +107,34 @@ def test_update_role_is_called_properly_on_each_action(
101107
):
102108
update_role_mock.return_value = {}
103109
response = client.post(
104-
f'/users/{user_id}/roles/{role_id}/{action}', headers=valid_header,
110+
f'/users/{user_id}/roles/{role_id}/{action}',
111+
headers=valid_header,
105112
)
106113

107114
assert HTTPStatus.OK == response.status_code
108115
update_role_mock.assert_called_once_with(
109116
user_id, role_id, is_grant=is_grant
110117
)
118+
119+
120+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
121+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
122+
@patch('utils.azure_users.AzureConnection.is_user_in_group')
123+
@mark.parametrize(
124+
'group_name, expected_value', [('admin', True), ('admin', False)]
125+
)
126+
def test_if_user_is_in_group(
127+
is_user_in_group_mock,
128+
client: FlaskClient,
129+
valid_header: dict,
130+
user_id: str,
131+
group_name,
132+
expected_value,
133+
):
134+
is_user_in_group_mock.return_value = {'value': expected_value}
135+
response = client.get(
136+
f'/users/{user_id}/groups/{group_name}/is-member-of',
137+
headers=valid_header,
138+
)
139+
assert HTTPStatus.OK == response.status_code
140+
assert 'value' in json.loads(response.data)

tests/utils/azure_users_test.py

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@
1414
],
1515
)
1616
def test_azure_connection_is_test_user(
17-
get_mock, field_name, field_value, is_test_user_expected_value,
17+
get_mock,
18+
field_name,
19+
field_value,
20+
is_test_user_expected_value,
1821
):
1922
response_mock = Mock()
2023
response_mock.status_code = 200
@@ -33,7 +36,12 @@ def test_azure_connection_get_test_user_ids(get_mock):
3336
response_mock = Mock()
3437
response_mock.status_code = 200
3538
response_mock.json = Mock(
36-
return_value={'value': [{'objectId': 'ID1'}, {'objectId': 'ID2'},]}
39+
return_value={
40+
'value': [
41+
{'objectId': 'ID1'},
42+
{'objectId': 'ID2'},
43+
]
44+
}
3745
)
3846
get_mock.return_value = response_mock
3947

@@ -56,3 +64,42 @@ def test_azure_connection_get_non_test_users(
5664
non_test_users = [non_test_user]
5765
az_conn = AzureConnection()
5866
assert az_conn.get_non_test_users() == non_test_users
67+
68+
69+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
70+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
71+
@patch('requests.get')
72+
def test_azure_connection_get_group_id_by_group_name(get_mock):
73+
response_mock = Mock()
74+
response_mock.status_code = 200
75+
response_mock.json = Mock(return_value={'value': [{'objectId': 'ID1'}]})
76+
get_mock.return_value = response_mock
77+
78+
group_id = 'ID1'
79+
azure_connection = AzureConnection()
80+
assert (
81+
azure_connection.get_group_id_by_group_name('group_name') == group_id
82+
)
83+
84+
85+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
86+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
87+
@patch('utils.azure_users.AzureConnection.get_group_id_by_group_name')
88+
@patch('requests.post')
89+
@mark.parametrize('expected_value', [True, False])
90+
def test_is_user_in_group(
91+
post_mock, get_group_id_by_group_name_mock, expected_value
92+
):
93+
response_expected = {'value': expected_value}
94+
response_mock = Mock()
95+
response_mock.status_code = 200
96+
response_mock.json = Mock(return_value=response_expected)
97+
post_mock.return_value = response_mock
98+
99+
get_group_id_by_group_name_mock.return_value = 'group_id'
100+
101+
azure_connection = AzureConnection()
102+
assert (
103+
azure_connection.is_user_in_group('user_id', 'group_name')
104+
== response_expected
105+
)

time_tracker_api/users/users_namespace.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,12 @@
3131
description='List of the roles assigned to the user by the tenant',
3232
),
3333
example=Faker().words(
34-
3, ['time-tracker-admin', 'test-user', 'guest',],
34+
3,
35+
[
36+
'time-tracker-admin',
37+
'test-user',
38+
'guest',
39+
],
3540
),
3641
),
3742
},
@@ -94,3 +99,13 @@ class RevokeRole(Resource):
9499
def post(self, user_id, role_id):
95100
"""Revoke role to user"""
96101
return AzureConnection().update_role(user_id, role_id, is_grant=False)
102+
103+
104+
@ns.route('/<string:user_id>/groups/<string:group_id>/is-member-of')
105+
@ns.param('user_id', 'The user identifier')
106+
@ns.param('group_id', 'The group name identifier')
107+
class UserInGroup(Resource):
108+
@ns.doc('user_in_group')
109+
def get(self, user_id, group_id):
110+
"""Is User in the Group"""
111+
return AzureConnection().is_user_in_group(user_id, group_id)

utils/azure_users.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,3 +169,35 @@ def get_test_user_ids(self):
169169
assert 200 == response.status_code
170170
assert 'value' in response.json()
171171
return [item['objectId'] for item in response.json()['value']]
172+
173+
def get_group_id_by_group_name(self, group_name):
174+
endpoint = "{endpoint}/groups?api-version=1.6&$select=objectId&$filter=displayName eq '{group_name}'".format(
175+
endpoint=self.config.ENDPOINT, group_name=group_name
176+
)
177+
178+
response = requests.get(endpoint, auth=BearerAuth(self.access_token))
179+
180+
assert 200 == response.status_code
181+
182+
return response.json()['value'][0]['objectId']
183+
184+
def is_user_in_group(self, user_id, group_name):
185+
group_id = self.get_group_id_by_group_name(group_name=group_name)
186+
187+
endpoint = "{endpoint}/isMemberOf?api-version=1.6".format(
188+
endpoint=self.config.ENDPOINT
189+
)
190+
191+
data = {"groupId": group_id, "memberId": user_id}
192+
193+
response = requests.post(
194+
endpoint,
195+
auth=BearerAuth(self.access_token),
196+
data=json.dumps(data),
197+
headers=HTTP_PATCH_HEADERS,
198+
)
199+
200+
assert 200 == response.status_code
201+
202+
item = response.json()['value']
203+
return {'value': item}

0 commit comments

Comments
 (0)