Skip to content

Users command: Functionality to add/remove roles + Users user guide #283

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 27, 2021
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
The intended audience of this file is for py42 consumers -- as such, changes that don't affect
how a consumer would use the library (e.g. adding unit tests, updating documentation, etc) are not captured here.

## Unreleased

### Added

- New command `code42 users add-role` to add a user role to a single user.

- New command `code42 users remove-role` to remove a user role from a single user.

## 1.5.1 - 2021-05-12

### Fixed
Expand Down
1 change: 1 addition & 0 deletions docs/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
* [High Risk Employee](commands/highriskemployee.rst)
* [Legal Hold](commands/legalhold.rst)
* [Cases](commands/cases.rst)
* [Users](commands/users.rst)
3 changes: 3 additions & 0 deletions docs/commands/users.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.. click:: code42cli.cmds.users:users
:prog: users
:show-nested:
45 changes: 45 additions & 0 deletions src/code42cli/cmds/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from code42cli.click_ext.groups import OrderedGroup
from code42cli.click_ext.options import incompatible_with
from code42cli.errors import Code42CLIError
from code42cli.errors import UserDoesNotExistError
from code42cli.options import format_option
from code42cli.options import sdk_options
from code42cli.output_formats import DataFrameOutputFormatter
Expand Down Expand Up @@ -60,6 +61,50 @@ def list_users(state, org_uid, role_name, active, inactive, format):
formatter.echo_formatted_dataframe(df)


@users.command()
@click.argument("username")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When commands have multiple arguments, we elect to use required options instead.
This is because then the order does not matter, like I don't have to use --help to see if the username goes first or the role name.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, I added them as functions with flexible help text, assuming that they would be re-used in further expansions of the users command.

@click.argument("role-name")
@sdk_options()
def add_role(state, username, role_name):
"""Add the specified role to the specified username"""
_add_user_role(state.sdk, role_name, username)


@users.command()
@click.argument("username")
@click.argument("role-name")
@sdk_options()
def remove_role(state, username, role_name):
"""Add the specified role to the specified username"""
_remove_user_role(state.sdk, role_name, username)


def _add_user_role(sdk, role_name, username):
user_id = _get_user_id(sdk, username)
try:
Copy link
Contributor

@antazoey antazoey May 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand what this try / except is doing.
It looks like it catches an error just to raise it again.
Is there any technical difference of just letting the error raise naturally? If so, we should add a comment explaining what it is because it is not obvious.

Copy link
Contributor Author

@maddie-vargo maddie-vargo May 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The try/except block isn't doing anything, actually - I will remove it.

We don't actually even need the roleID from _get_role_id to proceed. However, I like the error handling Cecilia had built for an invalid role name. It handles cases where an invalid role name was entered and provides the following error message:

Error: Role with name Fake_Role not found.

Otherwise, the error is:

Error: Problem making request to server.

Py42 doesn't really have a method for determining if a role name is valid.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth adding a comment about that, or even extracting the error handling into a separate method
(still approved but thought I'd mention that)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a comment. I had trouble extracting the error handling, since 95% of what that method doing is error handling. Extracting the error handling didn't add clarity or reduce redundancy.

_get_role_id(sdk, role_name)
except Code42CLIError:
raise
sdk.users.add_role(user_id, role_name)


def _remove_user_role(sdk, role_name, username):
user_id = _get_user_id(sdk, username)
try:
_get_role_id(sdk, role_name)
except Code42CLIError:
raise
sdk.users.remove_role(user_id, role_name)


def _get_user_id(sdk, username):
user = sdk.users.get_by_username(username)["users"]
if not user:
raise UserDoesNotExistError
user_id = user[0]["userId"]
return user_id


def _get_role_id(sdk, role_name):
try:
roles_dataframe = DataFrame.from_records(
Expand Down
49 changes: 49 additions & 0 deletions tests/cmds/test_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
}
]
}
TEST_USERNAME = TEST_USERS_RESPONSE["users"][0]["username"]
TEST_USER_ID = TEST_USERS_RESPONSE["users"][0]["userId"]
TEST_ROLE_NAME = TEST_ROLE_RETURN_DATA["data"][0]["roleName"]


def _create_py42_response(mocker, text):
Expand All @@ -56,6 +59,11 @@ def get_all_users_success(cli_state):
cli_state.sdk.users.get_all.return_value = get_all_users_generator()


@pytest.fixture
def get_user_id_success(cli_state):
cli_state.sdk.users.get_by_username.return_value = TEST_USERS_RESPONSE


@pytest.fixture
def get_available_roles_success(cli_state, get_available_roles_response):
cli_state.sdk.users.get_available_roles.return_value = get_available_roles_response
Expand Down Expand Up @@ -153,3 +161,44 @@ def test_list_users_when_given_excluding_active_and_inactive_uses_active_equals_
cli_state.sdk.users.get_all.assert_called_once_with(
active=None, org_uid=None, role_id=None
)


def test_add_user_role_adds(
runner, cli_state, get_user_id_success, get_available_roles_success
):
command = ["users", "add-role", "[email protected]", "Customer Cloud Admin"]
runner.invoke(cli, command, obj=cli_state)
cli_state.sdk.users.add_role.assert_called_once_with(TEST_USER_ID, TEST_ROLE_NAME)


def test_add_user_role_raises_error_when_role_does_not_exist(
runner, cli_state, get_user_id_success, get_available_roles_success
):
command = ["users", "add-role", "[email protected]", "test"]
result = runner.invoke(cli, command, obj=cli_state)
assert result.exit_code == 1
assert "Role with name test not found." in result.output


def test_remove_user_role_removes(
runner, cli_state, get_user_id_success, get_available_roles_success
):
command = [
"users",
"remove-role",
"[email protected]",
"Customer Cloud Admin",
]
runner.invoke(cli, command, obj=cli_state)
cli_state.sdk.users.remove_role.assert_called_once_with(
TEST_USER_ID, TEST_ROLE_NAME
)


def test_remove_user_role_raises_error_when_role_does_not_exist(
runner, cli_state, get_user_id_success, get_available_roles_success
):
command = ["users", "remove-role", "[email protected]", "test"]
result = runner.invoke(cli, command, obj=cli_state)
assert result.exit_code == 1
assert "Role with name test not found." in result.output