Skip to content

Commit a7a521f

Browse files
authored
Users command: Functionality to add/remove roles + Users user guide (#283)
1 parent 9cb18a9 commit a7a521f

File tree

3 files changed

+167
-5
lines changed

3 files changed

+167
-5
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
The intended audience of this file is for py42 consumers -- as such, changes that don't affect
99
how a consumer would use the library (e.g. adding unit tests, updating documentation, etc) are not captured here.
1010

11+
## Unreleased
12+
13+
### Added
14+
15+
- New command `code42 users add-role` to add a user role to a single user.
16+
17+
- New command `code42 users remove-role` to remove a user role from a single user.
18+
1119
## 1.6.1 - 2021-05-27
1220

1321
### Fixed

src/code42cli/cmds/users.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from code42cli.click_ext.groups import OrderedGroup
55
from code42cli.click_ext.options import incompatible_with
66
from code42cli.errors import Code42CLIError
7+
from code42cli.errors import UserDoesNotExistError
78
from code42cli.options import format_option
89
from code42cli.options import sdk_options
910
from code42cli.output_formats import DataFrameOutputFormatter
@@ -21,9 +22,6 @@ def users(state):
2122
"--org-uid",
2223
help="Limit users to only those in the organization you specify. Note that child orgs are included.",
2324
)
24-
role_name_option = click.option(
25-
"--role-name", help="Limit results to only users having the specified role.",
26-
)
2725
active_option = click.option(
2826
"--active", is_flag=True, help="Limits results to only active users.", default=None,
2927
)
@@ -35,9 +33,17 @@ def users(state):
3533
)
3634

3735

36+
def role_name_option(help):
37+
return click.option("--role-name", help=help)
38+
39+
40+
def username_option(help):
41+
return click.option("--username", help=help)
42+
43+
3844
@users.command(name="list")
3945
@org_uid_option
40-
@role_name_option
46+
@role_name_option("Limit results to only users having the specified role.")
4147
@active_option
4248
@inactive_option
4349
@format_option
@@ -60,6 +66,44 @@ def list_users(state, org_uid, role_name, active, inactive, format):
6066
formatter.echo_formatted_dataframe(df)
6167

6268

69+
@users.command()
70+
@username_option("Username of the target user.")
71+
@role_name_option("Name of role to add.")
72+
@sdk_options()
73+
def add_role(state, username, role_name):
74+
"""Add the specified role to the user with the specified username."""
75+
_add_user_role(state.sdk, username, role_name)
76+
77+
78+
@users.command()
79+
@role_name_option("Name of role to remove.")
80+
@username_option("Username of the target user.")
81+
@sdk_options()
82+
def remove_role(state, username, role_name):
83+
"""Remove the specified role to the user with the specified username."""
84+
_remove_user_role(state.sdk, role_name, username)
85+
86+
87+
def _add_user_role(sdk, username, role_name):
88+
user_id = _get_user_id(sdk, username)
89+
_get_role_id(sdk, role_name) # function provides role name validation
90+
sdk.users.add_role(user_id, role_name)
91+
92+
93+
def _remove_user_role(sdk, role_name, username):
94+
user_id = _get_user_id(sdk, username)
95+
_get_role_id(sdk, role_name) # function provides role name validation
96+
sdk.users.remove_role(user_id, role_name)
97+
98+
99+
def _get_user_id(sdk, username):
100+
user = sdk.users.get_by_username(username)["users"]
101+
if len(user) == 0:
102+
raise UserDoesNotExistError(username)
103+
user_id = user[0]["userId"]
104+
return user_id
105+
106+
63107
def _get_role_id(sdk, role_name):
64108
try:
65109
roles_dataframe = DataFrame.from_records(
@@ -68,7 +112,7 @@ def _get_role_id(sdk, role_name):
68112
role_result = roles_dataframe.at[role_name, "roleId"]
69113
return str(role_result) # extract the role ID from the series
70114
except KeyError:
71-
raise Code42CLIError(f"Role with name {role_name} not found.")
115+
raise Code42CLIError(f"Role with name '{role_name}' not found.")
72116

73117

74118
def _get_users_dataframe(sdk, columns, org_uid, role_id, active):

tests/cmds/test_users.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@
3232
}
3333
]
3434
}
35+
TEST_EMPTY_USERS_RESPONSE = {"users": []}
36+
TEST_USERNAME = TEST_USERS_RESPONSE["users"][0]["username"]
37+
TEST_USER_ID = TEST_USERS_RESPONSE["users"][0]["userId"]
38+
TEST_ROLE_NAME = TEST_ROLE_RETURN_DATA["data"][0]["roleName"]
3539

3640

3741
def _create_py42_response(mocker, text):
@@ -56,6 +60,16 @@ def get_all_users_success(cli_state):
5660
cli_state.sdk.users.get_all.return_value = get_all_users_generator()
5761

5862

63+
@pytest.fixture
64+
def get_user_id_success(cli_state):
65+
cli_state.sdk.users.get_by_username.return_value = TEST_USERS_RESPONSE
66+
67+
68+
@pytest.fixture
69+
def get_user_id_failure(cli_state):
70+
cli_state.sdk.users.get_by_username.return_value = TEST_EMPTY_USERS_RESPONSE
71+
72+
5973
@pytest.fixture
6074
def get_available_roles_success(cli_state, get_available_roles_response):
6175
cli_state.sdk.users.get_available_roles.return_value = get_available_roles_response
@@ -153,3 +167,99 @@ def test_list_users_when_given_excluding_active_and_inactive_uses_active_equals_
153167
cli_state.sdk.users.get_all.assert_called_once_with(
154168
active=None, org_uid=None, role_id=None
155169
)
170+
171+
172+
def test_add_user_role_adds(
173+
runner, cli_state, get_user_id_success, get_available_roles_success
174+
):
175+
command = [
176+
"users",
177+
"add-role",
178+
"--username",
179+
180+
"--role-name",
181+
"Customer Cloud Admin",
182+
]
183+
runner.invoke(cli, command, obj=cli_state)
184+
cli_state.sdk.users.add_role.assert_called_once_with(TEST_USER_ID, TEST_ROLE_NAME)
185+
186+
187+
def test_add_user_role_raises_error_when_role_does_not_exist(
188+
runner, cli_state, get_user_id_success, get_available_roles_success
189+
):
190+
command = [
191+
"users",
192+
"add-role",
193+
"--username",
194+
195+
"--role-name",
196+
"test",
197+
]
198+
result = runner.invoke(cli, command, obj=cli_state)
199+
assert result.exit_code == 1
200+
assert "Role with name 'test' not found." in result.output
201+
202+
203+
def test_add_user_role_raises_error_when_username_does_not_exist(
204+
runner, cli_state, get_user_id_failure, get_available_roles_success
205+
):
206+
command = [
207+
"users",
208+
"add-role",
209+
"--username",
210+
211+
"--role-name",
212+
"Desktop User",
213+
]
214+
result = runner.invoke(cli, command, obj=cli_state)
215+
assert result.exit_code == 1
216+
assert "User '[email protected]' does not exist." in result.output
217+
218+
219+
def test_remove_user_role_removes(
220+
runner, cli_state, get_user_id_success, get_available_roles_success
221+
):
222+
command = [
223+
"users",
224+
"remove-role",
225+
"--username",
226+
227+
"--role-name",
228+
"Customer Cloud Admin",
229+
]
230+
runner.invoke(cli, command, obj=cli_state)
231+
cli_state.sdk.users.remove_role.assert_called_once_with(
232+
TEST_USER_ID, TEST_ROLE_NAME
233+
)
234+
235+
236+
def test_remove_user_role_raises_error_when_role_does_not_exist(
237+
runner, cli_state, get_user_id_success, get_available_roles_success
238+
):
239+
command = [
240+
"users",
241+
"remove-role",
242+
"--username",
243+
244+
"--role-name",
245+
"test",
246+
]
247+
result = runner.invoke(cli, command, obj=cli_state)
248+
assert result.exit_code == 1
249+
assert "Role with name 'test' not found." in result.output
250+
251+
252+
def test_remove_user_role_raises_error_when_username_does_not_exist(
253+
runner, cli_state, get_user_id_failure, get_available_roles_success
254+
):
255+
command = [
256+
"users",
257+
"remove-role",
258+
"--username",
259+
260+
"--role-name",
261+
"Desktop User",
262+
]
263+
result = runner.invoke(cli, command, obj=cli_state)
264+
assert result.exit_code == 1
265+
assert "User '[email protected]' does not exist." in result.output

0 commit comments

Comments
 (0)