Skip to content

Commit f2b35a2

Browse files
authored
TT - 112 Update role field (#251)
* TT-112 feat: support role field as list * TT-112 feat: add feature toggle * TT-112 feat: add unit test, update role contains expected props * TT-112 feat: add unit testfor users endpoint under feature toggle * TT-112 feat: fix broken test * TT-112 fix: update role field value * TT-112 feat: apply comments of PR * TT-112 feat: update var name of feature manager
1 parent 49f601f commit f2b35a2

File tree

3 files changed

+139
-12
lines changed

3 files changed

+139
-12
lines changed

tests/time_tracker_api/users/users_namespace_test.py

Lines changed: 83 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,68 @@
66
from pytest import mark
77

88

9-
def test_users_response_contains_expected_props(
10-
client: FlaskClient, valid_header: dict,
9+
@patch(
10+
'commons.feature_toggles.feature_toggle_manager.FeatureToggleManager.get_azure_app_configuration_client'
11+
)
12+
@patch(
13+
'commons.feature_toggles.feature_toggle_manager.FeatureToggleManager.is_toggle_enabled_for_user'
14+
)
15+
@patch('utils.azure_users.AzureConnection.users')
16+
@patch('utils.azure_users.AzureConnection.users_v2')
17+
def test_feature_toggle_is_on_then_role_field_is_list(
18+
users_v2_mock,
19+
users_mock,
20+
is_toggle_enabled_for_user_mock,
21+
get_azure_app_configuration_client_mock,
22+
client: FlaskClient,
23+
valid_header: dict,
1124
):
1225

13-
AzureConnection.users = Mock(
14-
return_value=[{'name': 'dummy', 'email': 'dummy', 'role': 'dummy'}]
15-
)
26+
is_toggle_enabled_for_user_mock.return_value = True
27+
users_v2_mock.return_value = [
28+
{'name': 'dummy', 'email': 'dummy', 'roles': ['dummy-role']}
29+
]
30+
response = client.get('/users', headers=valid_header)
31+
32+
users_v2_mock.assert_called()
33+
users_mock.assert_not_called()
34+
assert HTTPStatus.OK == response.status_code
35+
assert 'name' in json.loads(response.data)[0]
36+
assert 'email' in json.loads(response.data)[0]
37+
assert 'roles' in json.loads(response.data)[0]
38+
assert ['dummy-role'] == json.loads(response.data)[0]['roles']
1639

17-
response = client.get('/users', headers=valid_header,)
1840

41+
@patch(
42+
'commons.feature_toggles.feature_toggle_manager.FeatureToggleManager.get_azure_app_configuration_client'
43+
)
44+
@patch(
45+
'commons.feature_toggles.feature_toggle_manager.FeatureToggleManager.is_toggle_enabled_for_user'
46+
)
47+
@patch('utils.azure_users.AzureConnection.users')
48+
@patch('utils.azure_users.AzureConnection.users_v2')
49+
def test_feature_toggle_is_off_then_role_field_is_string(
50+
users_v2_mock,
51+
users_mock,
52+
is_toggle_enabled_for_user_mock,
53+
get_azure_app_configuration_client_mock,
54+
client: FlaskClient,
55+
valid_header: dict,
56+
):
57+
is_toggle_enabled_for_user_mock.return_value = False
58+
users_mock.return_value = [
59+
{'name': 'dummy', 'email': 'dummy', 'role': 'dummy-role'}
60+
]
61+
62+
response = client.get('/users', headers=valid_header)
63+
64+
users_mock.assert_called()
65+
users_v2_mock.assert_not_called()
1966
assert HTTPStatus.OK == response.status_code
2067
assert 'name' in json.loads(response.data)[0]
2168
assert 'email' in json.loads(response.data)[0]
2269
assert 'role' in json.loads(response.data)[0]
70+
assert 'dummy-role' == json.loads(response.data)[0]['role']
2371

2472

2573
def test_update_user_role_response_contains_expected_props(
@@ -42,14 +90,40 @@ def test_update_user_role_response_contains_expected_props(
4290
assert 'role' in json.loads(response.data)
4391

4492

93+
@patch('utils.azure_users.AzureConnection.update_role')
94+
@mark.parametrize(
95+
'role_id,action', [('test', 'grant'), ('admin', 'revoke')],
96+
)
97+
def test_update_role_response_contains_expected_props(
98+
update_role_mock,
99+
client: FlaskClient,
100+
valid_header: dict,
101+
user_id: str,
102+
role_id,
103+
action,
104+
):
105+
update_role_mock.return_value = {
106+
'name': 'dummy',
107+
'email': 'dummy',
108+
'roles': [],
109+
}
110+
response = client.post(
111+
f'/users/{user_id}/roles/{role_id}/{action}', headers=valid_header,
112+
)
113+
assert HTTPStatus.OK == response.status_code
114+
assert 'name' in json.loads(response.data)
115+
assert 'email' in json.loads(response.data)
116+
assert 'roles' in json.loads(response.data)
117+
118+
45119
@patch('utils.azure_users.AzureConnection.update_user_role', new_callable=Mock)
46120
def test_on_post_update_user_role_is_being_called_with_valid_arguments(
47121
update_user_role_mock,
48122
client: FlaskClient,
49123
valid_header: dict,
50124
user_id: str,
51125
):
52-
126+
update_user_role_mock.return_value = {}
53127
valid_user_role_data = {'role': 'admin'}
54128
response = client.post(
55129
f'/users/{user_id}/roles',
@@ -70,7 +144,7 @@ def test_on_delete_update_user_role_is_being_called_with_valid_arguments(
70144
valid_header: dict,
71145
user_id: str,
72146
):
73-
147+
update_user_role_mock.return_value = {}
74148
response = client.delete(
75149
f'/users/{user_id}/roles/time-tracker-admin', headers=valid_header,
76150
)
@@ -98,6 +172,7 @@ def test_update_role_is_called_properly_on_each_action(
98172
action,
99173
is_grant,
100174
):
175+
update_role_mock.return_value = {}
101176
response = client.post(
102177
f'/users/{user_id}/roles/{role_id}/{action}', headers=valid_header,
103178
)

time_tracker_api/users/users_namespace.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from time_tracker_api.api import common_fields, api, NullableString
66

77
from utils.azure_users import AzureConnection
8-
8+
from commons.feature_toggles.feature_toggle_manager import FeatureToggleManager
99

1010
ns = api.namespace('users', description='Namespace of the API for users')
1111

@@ -32,6 +32,15 @@
3232
description='Role assigned to the user by the tenant',
3333
example=Faker().word(['time-tracker-admin']),
3434
),
35+
'roles': fields.List(
36+
fields.String(
37+
title='Roles',
38+
description='List of the roles assigned to the user by the tenant',
39+
),
40+
example=Faker().words(
41+
3, ['time-tracker-admin', 'test-user', 'guest',],
42+
),
43+
),
3544
},
3645
)
3746

@@ -57,6 +66,9 @@ class Users(Resource):
5766
@ns.marshal_list_with(user_response_fields)
5867
def get(self):
5968
"""List all users"""
69+
user_role_field_toggle = FeatureToggleManager('bk-user-role-field')
70+
if user_role_field_toggle.is_toggle_enabled_for_user():
71+
return AzureConnection().users_v2()
6072
return AzureConnection().users()
6173

6274

utils/azure_users.py

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,14 @@ def __init__(self, id, name, email, role):
4545
self.role = role
4646

4747

48+
class AzureUser_v2:
49+
def __init__(self, id, name, email, roles):
50+
self.id = id
51+
self.name = name
52+
self.email = email
53+
self.roles = roles
54+
55+
4856
HTTP_PATCH_HEADERS = {
4957
'Content-type': 'application/json',
5058
'Accept': 'application/json',
@@ -53,9 +61,12 @@ def __init__(self, id, name, email, role):
5361
ROLE_FIELD_VALUES = {
5462
'admin': (
5563
'extension_1d76efa96f604499acc0c0ee116a1453_role',
56-
'time_tracker_admin',
64+
'time-tracker-admin',
65+
),
66+
'test': (
67+
'extension_1d76efa96f604499acc0c0ee116a1453_role_test',
68+
'time-tracker-tester',
5769
),
58-
'test': ('waitforrealvalue', 'waitforrealvalue'),
5970
}
6071

6172

@@ -91,6 +102,22 @@ def users(self) -> List[AzureUser]:
91102
assert 'value' in response.json()
92103
return [self.to_azure_user(item) for item in response.json()['value']]
93104

105+
def users_v2(self) -> List[AzureUser]:
106+
role_fields_params = ','.join(
107+
[field_name for field_name, _ in ROLE_FIELD_VALUES.values()]
108+
)
109+
endpoint = "{endpoint}/users?api-version=1.6&$select=displayName,otherMails,objectId,{role_fields_params}".format(
110+
endpoint=self.config.ENDPOINT,
111+
role_fields_params=role_fields_params,
112+
)
113+
response = requests.get(endpoint, auth=BearerAuth(self.access_token))
114+
115+
assert 200 == response.status_code
116+
assert 'value' in response.json()
117+
return [
118+
self.to_azure_user_v2(item) for item in response.json()['value']
119+
]
120+
94121
def update_user_role(self, id, role):
95122
endpoint = "{endpoint}/users/{user_id}?api-version=1.6".format(
96123
endpoint=self.config.ENDPOINT, user_id=id
@@ -119,6 +146,19 @@ def to_azure_user(self, item) -> AzureUser:
119146
role = item[self.role_field] if there_is_role else None
120147
return AzureUser(id, name, email, role)
121148

149+
def to_azure_user_v2(self, item) -> AzureUser_v2:
150+
there_is_email = len(item['otherMails']) > 0
151+
152+
id = item['objectId']
153+
name = item['displayName']
154+
email = item['otherMails'][0] if there_is_email else ''
155+
roles = [
156+
item[field_name]
157+
for (field_name, field_value) in ROLE_FIELD_VALUES.values()
158+
if field_name in item
159+
]
160+
return AzureUser_v2(id, name, email, roles)
161+
122162
def update_role(self, user_id, role_id, is_grant):
123163
endpoint = "{endpoint}/users/{user_id}?api-version=1.6".format(
124164
endpoint=self.config.ENDPOINT, user_id=user_id
@@ -136,7 +176,7 @@ def update_role(self, user_id, role_id, is_grant):
136176
response = requests.get(endpoint, auth=BearerAuth(self.access_token))
137177
assert 200 == response.status_code
138178

139-
return self.to_azure_user(response.json())
179+
return self.to_azure_user_v2(response.json())
140180

141181
def get_role_data(self, role_id, is_grant=True):
142182
assert role_id in ROLE_FIELD_VALUES.keys()

0 commit comments

Comments
 (0)