Skip to content

Commit e26da51

Browse files
List users (#270)
1 parent 0dbed1e commit e26da51

File tree

4 files changed

+192
-0
lines changed

4 files changed

+192
-0
lines changed

CHANGELOG.md

+7
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1010

1111
## 1.4.2 - 2021-04-22
1212

13+
### Added
14+
15+
- New command `code42 users list` with options:
16+
- `--org-uid` filters on org membership.
17+
- `--role-name` filters on users having a particular role.
18+
- `--active` and `--inactive` filter on user status.
19+
1320
### Fixed
1421

1522
- Bug where some CSV outputs on Windows would have an extra newline between the rows.

src/code42cli/cmds/users.py

+73
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import click
2+
from pandas import DataFrame
3+
4+
from code42cli.click_ext.groups import OrderedGroup
5+
from code42cli.click_ext.options import incompatible_with
6+
from code42cli.errors import Code42CLIError
7+
from code42cli.options import format_option
8+
from code42cli.options import sdk_options
9+
from code42cli.output_formats import DataFrameOutputFormatter
10+
11+
12+
@click.group(cls=OrderedGroup)
13+
@sdk_options(hidden=True)
14+
def users(state):
15+
"""Manage users within your Code42 environment."""
16+
pass
17+
18+
19+
org_uid_option = click.option(
20+
"--org-uid",
21+
help="Limit users to only those in the organization you specify. Note that child orgs are included.",
22+
)
23+
role_name_option = click.option(
24+
"--role-name", help="Limit results to only users having the specified role.",
25+
)
26+
active_option = click.option(
27+
"--active", is_flag=True, help="Limits results to only active users.", default=None,
28+
)
29+
inactive_option = click.option(
30+
"--inactive",
31+
is_flag=True,
32+
help="Limits results to only deactivated users.",
33+
cls=incompatible_with("active"),
34+
)
35+
36+
37+
@users.command(name="list")
38+
@org_uid_option
39+
@role_name_option
40+
@active_option
41+
@inactive_option
42+
@format_option
43+
@sdk_options()
44+
def list_users(state, org_uid, role_name, active, inactive, format):
45+
"""List users in your Code42 environment."""
46+
if inactive:
47+
active = False
48+
role_id = _get_role_id(state.sdk, role_name) if role_name else None
49+
df = _get_users_dataframe(state.sdk, org_uid, role_id, active)
50+
if df.empty:
51+
click.echo("No results found.")
52+
else:
53+
formatter = DataFrameOutputFormatter(format)
54+
formatter.echo_formatted_dataframe(df)
55+
56+
57+
def _get_role_id(sdk, role_name):
58+
try:
59+
roles_dataframe = DataFrame.from_records(
60+
sdk.users.get_available_roles().data, index="roleName"
61+
)
62+
role_result = roles_dataframe.at[role_name, "roleId"]
63+
return str(role_result) # extract the role ID from the series
64+
except KeyError:
65+
raise Code42CLIError(f"Role with name {role_name} not found.")
66+
67+
68+
def _get_users_dataframe(sdk, org_uid, role_id, active):
69+
users_generator = sdk.users.get_all(active=active, org_uid=org_uid, role_id=role_id)
70+
users_list = []
71+
for page in users_generator:
72+
users_list.extend(page["users"])
73+
return DataFrame.from_records(users_list)

src/code42cli/main.py

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from code42cli.cmds.legal_hold import legal_hold
2121
from code42cli.cmds.profile import profile
2222
from code42cli.cmds.securitydata import security_data
23+
from code42cli.cmds.users import users
2324
from code42cli.options import sdk_options
2425

2526
BANNER = """\b
@@ -80,5 +81,6 @@ def cli(state, python):
8081
cli.add_command(legal_hold)
8182
cli.add_command(profile)
8283
cli.add_command(devices)
84+
cli.add_command(users)
8385
cli.add_command(audit_logs)
8486
cli.add_command(cases)

tests/cmds/test_users.py

+110
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import json
2+
3+
import pytest
4+
from py42.response import Py42Response
5+
from requests import Response
6+
7+
from code42cli.main import cli
8+
9+
TEST_ROLE_RETURN_DATA = {
10+
"data": [{"roleName": "Customer Cloud Admin", "roleId": "1234543"}]
11+
}
12+
13+
TEST_USERS_RESPONSE = {
14+
"users": [
15+
{
16+
"userId": 1234,
17+
"userUid": "997962681513153325",
18+
"status": "Active",
19+
"username": "[email protected]",
20+
"creationDate": "2021-03-12T20:07:40.898Z",
21+
"modificationDate": "2021-03-12T20:07:40.938Z",
22+
}
23+
]
24+
}
25+
26+
27+
def _create_py42_response(mocker, text):
28+
response = mocker.MagicMock(spec=Response)
29+
response.text = text
30+
response._content_consumed = mocker.MagicMock()
31+
response.status_code = 200
32+
return Py42Response(response)
33+
34+
35+
def get_all_users_generator():
36+
yield TEST_USERS_RESPONSE
37+
38+
39+
@pytest.fixture
40+
def get_available_roles_response(mocker):
41+
return _create_py42_response(mocker, json.dumps(TEST_ROLE_RETURN_DATA))
42+
43+
44+
@pytest.fixture
45+
def get_all_users_success(cli_state):
46+
cli_state.sdk.users.get_all.return_value = get_all_users_generator()
47+
48+
49+
@pytest.fixture
50+
def get_available_roles_success(cli_state, get_available_roles_response):
51+
cli_state.sdk.users.get_available_roles.return_value = get_available_roles_response
52+
53+
54+
def test_list_outputs_appropriate_columns(runner, cli_state, get_all_users_success):
55+
result = runner.invoke(cli, ["users", "list"], obj=cli_state)
56+
assert "userId" in result.output
57+
assert "userUid" in result.output
58+
assert "status" in result.output
59+
assert "username" in result.output
60+
assert "creationDate" in result.output
61+
assert "modificationDate" in result.output
62+
63+
64+
def test_list_users_calls_users_get_all_with_expected_role_id(
65+
runner, cli_state, get_available_roles_success, get_all_users_success
66+
):
67+
ROLE_NAME = "Customer Cloud Admin"
68+
runner.invoke(cli, ["users", "list", "--role-name", ROLE_NAME], obj=cli_state)
69+
cli_state.sdk.users.get_all.assert_called_once_with(
70+
active=None, org_uid=None, role_id="1234543"
71+
)
72+
73+
74+
def test_list_users_calls_get_all_users_with_correct_parameters(
75+
runner, cli_state, get_all_users_success
76+
):
77+
org_uid = "TEST_ORG_UID"
78+
runner.invoke(
79+
cli, ["users", "list", "--org-uid", org_uid, "--active"], obj=cli_state
80+
)
81+
cli_state.sdk.users.get_all.assert_called_once_with(
82+
active=True, org_uid=org_uid, role_id=None
83+
)
84+
85+
86+
def test_list_users_when_given_inactive_uses_active_equals_false(
87+
runner, cli_state, get_available_roles_success, get_all_users_success
88+
):
89+
runner.invoke(cli, ["users", "list", "--inactive"], obj=cli_state)
90+
cli_state.sdk.users.get_all.assert_called_once_with(
91+
active=False, org_uid=None, role_id=None
92+
)
93+
94+
95+
def test_list_users_when_given_active_and_inactive_raises_error(
96+
runner, cli_state, get_available_roles_success, get_all_users_success
97+
):
98+
result = runner.invoke(
99+
cli, ["users", "list", "--active", "--inactive"], obj=cli_state
100+
)
101+
assert "Error: --inactive can't be used with: --active" in result.output
102+
103+
104+
def test_list_users_when_given_excluding_active_and_inactive_uses_active_equals_none(
105+
runner, cli_state, get_available_roles_success, get_all_users_success
106+
):
107+
runner.invoke(cli, ["users", "list"], obj=cli_state)
108+
cli_state.sdk.users.get_all.assert_called_once_with(
109+
active=None, org_uid=None, role_id=None
110+
)

0 commit comments

Comments
 (0)