Skip to content

Commit 44b0885

Browse files
committed
Revert "Revert "229 make admin""
1 parent 2a85e2c commit 44b0885

File tree

4 files changed

+147
-41
lines changed

4 files changed

+147
-41
lines changed

tests/conftest.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,11 @@ def owner_id() -> str:
140140
return fake.uuid4()
141141

142142

143+
@pytest.fixture(scope="session")
144+
def user_id() -> str:
145+
return fake.uuid4()
146+
147+
143148
@pytest.fixture(scope="function")
144149
def sample_item(
145150
cosmos_db_repository: CosmosDBRepository,
Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,78 @@
1-
from unittest.mock import Mock
1+
from unittest.mock import Mock, patch
22
from flask import json
33
from flask.testing import FlaskClient
44
from flask_restplus._http import HTTPStatus
55
from utils.azure_users import AzureConnection
66

77

88
def test_users_response_contains_expected_props(
9-
client: FlaskClient,
10-
valid_header: dict,
9+
client: FlaskClient, valid_header: dict,
1110
):
1211

1312
AzureConnection.users = Mock(
1413
return_value=[{'name': 'dummy', 'email': 'dummy', 'role': 'dummy'}]
1514
)
1615

17-
response = client.get(
18-
'/users',
19-
headers=valid_header,
20-
)
16+
response = client.get('/users', headers=valid_header,)
2117

2218
assert HTTPStatus.OK == response.status_code
2319
assert 'name' in json.loads(response.data)[0]
2420
assert 'email' in json.loads(response.data)[0]
2521
assert 'role' in json.loads(response.data)[0]
22+
23+
24+
def test_update_user_role_response_contains_expected_props(
25+
client: FlaskClient, valid_header: dict, user_id: str,
26+
):
27+
valid_user_role_data = {'role': 'admin'}
28+
AzureConnection.update_user_role = Mock(
29+
return_value={'name': 'dummy', 'email': 'dummy', 'role': 'dummy'}
30+
)
31+
32+
response = client.post(
33+
f'/users/{user_id}/roles',
34+
headers=valid_header,
35+
json=valid_user_role_data,
36+
)
37+
38+
assert HTTPStatus.OK == response.status_code
39+
assert 'name' in json.loads(response.data)
40+
assert 'email' in json.loads(response.data)
41+
assert 'role' in json.loads(response.data)
42+
43+
44+
@patch('utils.azure_users.AzureConnection.update_user_role', new_callable=Mock)
45+
def test_on_post_update_user_role_is_being_called_with_valid_arguments(
46+
update_user_role_mock,
47+
client: FlaskClient,
48+
valid_header: dict,
49+
user_id: str,
50+
):
51+
52+
valid_user_role_data = {'role': 'admin'}
53+
response = client.post(
54+
f'/users/{user_id}/roles',
55+
headers=valid_header,
56+
json=valid_user_role_data,
57+
)
58+
59+
assert HTTPStatus.OK == response.status_code
60+
update_user_role_mock.assert_called_once_with(
61+
user_id, valid_user_role_data['role']
62+
)
63+
64+
65+
@patch('utils.azure_users.AzureConnection.update_user_role', new_callable=Mock)
66+
def test_on_delete_update_user_role_is_being_called_with_valid_arguments(
67+
update_user_role_mock,
68+
client: FlaskClient,
69+
valid_header: dict,
70+
user_id: str,
71+
):
72+
73+
response = client.delete(
74+
f'/users/{user_id}/roles/time-tracker-admin', headers=valid_header,
75+
)
76+
77+
assert HTTPStatus.OK == response.status_code
78+
update_user_role_mock.assert_called_once_with(user_id, role=None)

time_tracker_api/users/users_namespace.py

Lines changed: 45 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
from flask_restplus import fields, Resource
33
from flask_restplus._http import HTTPStatus
44

5-
from time_tracker_api.api import common_fields, api
5+
from time_tracker_api.api import common_fields, api, NullableString
66

7-
faker = Faker()
7+
from utils.azure_users import AzureConnection
8+
9+
10+
azure_connection = AzureConnection()
811

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

@@ -17,45 +20,72 @@
1720
title='Name',
1821
max_length=50,
1922
description='Name of the user',
20-
example=faker.word(['Marcelo', 'Sandro']),
23+
example=Faker().word(['Marcelo', 'Sandro']),
2124
),
2225
'email': fields.String(
2326
title="User's Email",
2427
max_length=50,
2528
description='Email of the user that belongs to the tenant',
26-
example=faker.email(),
29+
example=Faker().email(),
2730
),
28-
'role': fields.String(
31+
'role': NullableString(
2932
title="User's Role",
3033
max_length=50,
3134
description='Role assigned to the user by the tenant',
32-
example=faker.word(['admin']),
35+
example=Faker().word(['time-tracker-admin']),
3336
),
3437
},
3538
)
3639

3740
user_response_fields.update(common_fields)
3841

42+
user_role_input_fields = ns.model(
43+
'UserRoleInput',
44+
{
45+
'role': NullableString(
46+
title="User's Role",
47+
required=True,
48+
max_length=50,
49+
description='Role assigned to the user by the tenant',
50+
example=Faker().word(['time-tracker-admin']),
51+
),
52+
},
53+
)
54+
3955

4056
@ns.route('')
4157
class Users(Resource):
4258
@ns.doc('list_users')
4359
@ns.marshal_list_with(user_response_fields)
4460
def get(self):
4561
"""List all users"""
46-
from utils.azure_users import AzureConnection
47-
48-
azure_connection = AzureConnection()
4962
return azure_connection.users()
5063

5164

52-
@ns.route('/<string:id>')
65+
@ns.route('/<string:id>/roles')
5366
@ns.response(HTTPStatus.NOT_FOUND, 'User not found')
5467
@ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The id has an invalid format')
5568
@ns.param('id', 'The user identifier')
56-
class User(Resource):
57-
@ns.doc('get_user')
69+
class UserRoles(Resource):
70+
@ns.doc('create_user_role')
71+
@ns.expect(user_role_input_fields)
72+
@ns.response(
73+
HTTPStatus.BAD_REQUEST, 'Invalid format or structure of the user'
74+
)
75+
@ns.marshal_with(user_response_fields)
76+
def post(self, id):
77+
"""Create user's role"""
78+
return azure_connection.update_user_role(id, ns.payload['role'])
79+
80+
81+
@ns.route('/<string:user_id>/roles/<string:role_id>')
82+
@ns.response(HTTPStatus.NOT_FOUND, 'User not found')
83+
@ns.response(HTTPStatus.UNPROCESSABLE_ENTITY, 'The id has an invalid format')
84+
@ns.param('user_id', 'The user identifier')
85+
@ns.param('role_id', 'The role name identifier')
86+
class UserRole(Resource):
87+
@ns.doc('delete_user_role')
5888
@ns.marshal_with(user_response_fields)
59-
def get(self, id):
60-
"""Get an user"""
61-
return {}
89+
def delete(self, user_id, role_id):
90+
"""Delete user's role"""
91+
return azure_connection.update_user_role(user_id, role=None)

utils/azure_users.py

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import msal
22
import os
33
import requests
4+
import json
45
from typing import List
56

67

@@ -54,6 +55,8 @@ def __init__(self, config=MSConfig):
5455
self.config = config
5556
self.access_token = self.get_token()
5657

58+
self.role_field = 'extension_1d76efa96f604499acc0c0ee116a1453_role'
59+
5760
def get_token(self):
5861
response = self.client.acquire_token_for_client(
5962
scopes=self.config.SCOPE
@@ -65,28 +68,43 @@ def get_token(self):
6568
raise ValueError(error_info)
6669

6770
def users(self) -> List[AzureUser]:
68-
def to_azure_user(item) -> AzureUser:
69-
there_is_email = len(item['otherMails']) > 0
70-
there_is_role = (
71-
'extension_1d76efa96f604499acc0c0ee116a1453_role' in item
72-
)
73-
74-
id = item['objectId']
75-
name = item['displayName']
76-
email = item['otherMails'][0] if there_is_email else ''
77-
role = (
78-
item['extension_1d76efa96f604499acc0c0ee116a1453_role']
79-
if there_is_role
80-
else None
81-
)
82-
return AzureUser(id, name, email, role)
83-
8471
endpoint = "{endpoint}/users?api-version=1.6&$select=displayName,otherMails,objectId,{role_field}".format(
85-
endpoint=self.config.ENDPOINT,
86-
role_field='extension_1d76efa96f604499acc0c0ee116a1453_role',
72+
endpoint=self.config.ENDPOINT, role_field=self.role_field,
8773
)
8874
response = requests.get(endpoint, auth=BearerAuth(self.access_token))
8975

9076
assert 200 == response.status_code
9177
assert 'value' in response.json()
92-
return [to_azure_user(item) for item in response.json()['value']]
78+
return [self.to_azure_user(item) for item in response.json()['value']]
79+
80+
def update_user_role(self, id, role):
81+
headers = {
82+
'Content-type': 'application/json',
83+
'Accept': 'application/json',
84+
}
85+
endpoint = "{endpoint}/users/{user_id}?api-version=1.6".format(
86+
endpoint=self.config.ENDPOINT, user_id=id
87+
)
88+
data = {self.role_field: role}
89+
response = requests.patch(
90+
endpoint,
91+
auth=BearerAuth(self.access_token),
92+
data=json.dumps(data),
93+
headers=headers,
94+
)
95+
assert 204 == response.status_code
96+
97+
response = requests.get(endpoint, auth=BearerAuth(self.access_token))
98+
assert 200 == response.status_code
99+
100+
return self.to_azure_user(response.json())
101+
102+
def to_azure_user(self, item) -> AzureUser:
103+
there_is_email = len(item['otherMails']) > 0
104+
there_is_role = self.role_field in item
105+
106+
id = item['objectId']
107+
name = item['displayName']
108+
email = item['otherMails'][0] if there_is_email else ''
109+
role = item[self.role_field] if there_is_role else None
110+
return AzureUser(id, name, email, role)

0 commit comments

Comments
 (0)