Skip to content

Commit 9ab3328

Browse files
Juliya Smithtimabrmsn
andauthored
Feature/select profile (#262)
* Add select command * add cl * Rm fstr * PR Feedback * CL for profile use change * Add missing test and cl entry * A and The * fstr * refactor * rm conflict markers * add tests for prompt choice class * test prompt text * rm accidental part of cl * Add word was * cond * Doc * spell out var * add eg * rename method * use helper method * doc * rm unused fixtures * rm var from str intp in test * rm extra space * conform modular naming * fix tests * doc * undo unreleated changes * Undo unreleated changes pt2 * hmmm..... * add missing echo * undo and actually add missing echo * correct changelog * clarify Co-authored-by: tim.abramson <tim.abramson@code42.com>
1 parent 4ca646d commit 9ab3328

10 files changed

Lines changed: 111 additions & 8 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@ how a consumer would use the library (e.g. adding unit tests, updating documenta
1414

1515
- New option `--include-legal-hold-membership` on command `code42 users list` that includes the legal hold matter name and ID for any user on legal hold.
1616

17-
- New commands:
17+
- New commands for deactivating/reactivating Code42 user accounts:
1818
- `code42 users deactivate`
1919
- `code42 users reactivate`
2020
- `code42 users bulk deactivate`
2121
- `code42 users bulk reactivate`
2222

23+
- `code42 profile use` now prompts you to select a profile when not given a profile name argument.
24+
2325
## 1.9.0 - 2021-08-19
2426

2527
### Added

src/code42cli/click_ext/types.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from click.exceptions import BadParameter
99

1010
from code42cli.logger import CliLogger
11+
from code42cli.util import print_numbered_list
1112

1213

1314
class AutoDecodedFile(click.File):
@@ -151,6 +152,21 @@ def convert(self, value, param, ctx):
151152
return super().convert(value, param, ctx)
152153

153154

155+
class PromptChoice(click.ParamType):
156+
def __init__(self, choices):
157+
self.choices = choices
158+
159+
def print_choices(self):
160+
print_numbered_list(self.choices)
161+
162+
def convert(self, value, param, ctx):
163+
try:
164+
choice_index = int(value) - 1
165+
return self.choices[choice_index]
166+
except Exception:
167+
self.fail("Invalid choice", param=param)
168+
169+
154170
class TOTP(click.ParamType):
155171
"""Validates param to be a 6-digit integer, which is what all Code42 TOTP tokens will be."""
156172

src/code42cli/cmds/auditlogs.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ def _get_audit_logs_default_header():
4949
help="Filter results by actor user IDs.",
5050
multiple=True,
5151
)
52-
5352
filter_option_user_ip_addresses = click.option(
5453
"--actor-ip",
5554
required=False,

src/code42cli/cmds/profile.py

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from click import secho
66

77
import code42cli.profile as cliprofile
8+
from code42cli.click_ext.types import PromptChoice
89
from code42cli.click_ext.types import TOTP
910
from code42cli.errors import Code42CLIError
1011
from code42cli.options import yes_option
@@ -158,9 +159,15 @@ def _list():
158159
@profile.command()
159160
@profile_name_arg()
160161
def use(profile_name):
161-
"""Set a profile as the default."""
162-
cliprofile.switch_default_profile(profile_name)
163-
echo(f"{profile_name} has been set as the default profile.")
162+
"""\b
163+
Set a profile as the default. If not providing a profile-name,
164+
prompts for a choice from a list of all profiles."""
165+
166+
if not profile_name:
167+
_select_profile_from_prompt()
168+
return
169+
170+
_set_default_profile(profile_name)
164171

165172

166173
@profile.command()
@@ -219,3 +226,23 @@ def _set_pw(profile_name, password, debug, totp=None):
219226
raise
220227
cliprofile.set_password(password, c42profile.name)
221228
return c42profile.name
229+
230+
231+
def _select_profile_from_prompt():
232+
"""Set the default profile from user input."""
233+
profiles = cliprofile.get_all_profiles()
234+
profile_names = [profile_choice.name for profile_choice in profiles]
235+
choices = PromptChoice(profile_names)
236+
choices.print_choices()
237+
prompt_message = "Input the number of the profile you wish to use"
238+
profile_name = click.prompt(prompt_message, type=choices)
239+
_set_default_profile(profile_name)
240+
241+
242+
def _set_default_profile(profile_name):
243+
cliprofile.switch_default_profile(profile_name)
244+
_print_default_profile_was_set(profile_name)
245+
246+
247+
def _print_default_profile_was_set(profile_name):
248+
echo(f"{profile_name} has been set as the default profile.")

src/code42cli/util.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ def get_user_project_path(*subdirs):
3838
result_path = path.join(user_project_path, *subdirs)
3939
if not path.exists(result_path):
4040
os.makedirs(result_path)
41+
4142
return result_path
4243

4344

@@ -156,6 +157,7 @@ def get_url_parts(url_str):
156157
port = None
157158
if len(parts) > 1 and parts[1] != "":
158159
port = int(parts[1])
160+
159161
return parts[0], port
160162

161163

@@ -170,6 +172,7 @@ def _get_default_header(header_items):
170172
for key in keys:
171173
if key not in header and isinstance(key, str):
172174
header[key] = key
175+
173176
return header
174177

175178

@@ -179,6 +182,17 @@ def hash_event(event):
179182
return md5(event.encode()).hexdigest()
180183

181184

185+
def print_numbered_list(items):
186+
"""Outputs a numbered list of items to the user.
187+
For example, provide ["test", "foo"] to print "1. test\n2. foo".
188+
"""
189+
190+
choices = dict(enumerate(items, 1))
191+
for num in choices:
192+
echo(f"{num}. {choices[num]}")
193+
echo()
194+
195+
182196
def parse_timestamp(date_str):
183197
# example: {"property": "bar", "timestamp": "2020-11-23T17:13:26.239647Z"}
184198
ts = date_str[:-1]

tests/click_ext/__init__.py

Whitespace-only changes.

tests/click_ext/test_types.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from code42cli.click_ext.types import PromptChoice
2+
3+
4+
class TestPromptChoice:
5+
def test_convert_returns_expected_item(self):
6+
choices = ["foo", "bar", "test"]
7+
prompt_choice = PromptChoice(choices)
8+
assert prompt_choice.convert("1", None, None) == "foo"
9+
assert prompt_choice.convert("2", None, None) == "bar"
10+
assert prompt_choice.convert("3", None, None) == "test"

tests/cmds/test_auditlogs.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,8 @@ def audit_log_cursor_with_checkpoint(mocker):
6666
mock_cursor = mocker.MagicMock(spec=AuditLogCursorStore)
6767
mock_cursor.get.return_value = CURSOR_TIMESTAMP
6868
mocker.patch(
69-
"code42cli.cmds.auditlogs._get_audit_log_cursor_store", return_value=mock_cursor
69+
"code42cli.cmds.auditlogs._get_audit_log_cursor_store",
70+
return_value=mock_cursor,
7071
)
7172
return mock_cursor
7273

@@ -79,7 +80,8 @@ def audit_log_cursor_with_checkpoint_and_events(mocker):
7980
hash_event(TEST_EVENTS_WITH_SAME_TIMESTAMP[0])
8081
]
8182
mocker.patch(
82-
"code42cli.cmds.auditlogs._get_audit_log_cursor_store", return_value=mock_cursor
83+
"code42cli.cmds.auditlogs._get_audit_log_cursor_store",
84+
return_value=mock_cursor,
8385
)
8486
return mock_cursor
8587

tests/cmds/test_profile.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
from code42cli.main import cli
88

99

10+
_SELECTED_PROFILE_NAME = "test_profile"
11+
12+
1013
@pytest.fixture
1114
def user_agreement(mocker):
1215
mock = mocker.patch("code42cli.cmds.profile.does_user_agree")
@@ -50,6 +53,13 @@ def invalid_connection(mock_verify):
5053
return mock_verify
5154

5255

56+
@pytest.fixture
57+
def profile_name_selector(mocker):
58+
mock = mocker.patch("code42cli.cmds.profile.click.prompt")
59+
mock.return_value = _SELECTED_PROFILE_NAME
60+
return mock
61+
62+
5363
def test_show_profile_outputs_profile_info(runner, mock_cliprofile_namespace, profile):
5464
profile.name = "testname"
5565
profile.authority_url = "example.com"
@@ -511,6 +521,29 @@ def test_use_profile(runner, mock_cliprofile_namespace, profile):
511521
assert f"{profile.name} has been set as the default profile." in result.output
512522

513523

524+
def test_use_profile_when_not_given_profile_name_arg_sets_selected_profile_as_default(
525+
runner, mock_cliprofile_namespace, profile_name_selector
526+
):
527+
runner.invoke(cli, ["profile", "use"])
528+
mock_cliprofile_namespace.switch_default_profile.assert_called_once_with(
529+
_SELECTED_PROFILE_NAME
530+
)
531+
532+
533+
def test_use_profile_when_not_given_profile_name_outputs_expected_text(
534+
runner, mock_cliprofile_namespace, profile_name_selector
535+
):
536+
mock_cliprofile_namespace.get_all_profiles.return_value = [
537+
create_mock_profile("test1"),
538+
create_mock_profile("test2"),
539+
]
540+
result = runner.invoke(cli, ["profile", "use"])
541+
expected_prompt = "1. test1\n2. test2"
542+
expected_result_message = "test_profile has been set as the default profile."
543+
assert expected_prompt in result.output
544+
assert expected_result_message in result.output
545+
546+
514547
def test_totp_option_passes_token_to_sdk_on_profile_cmds_that_init_sdk(
515548
runner, mocker, mock_cliprofile_namespace, cli_state
516549
):

tests/test_util.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def test_does_user_agree_when_user_says_n_returns_false(
6161

6262

6363
def test_does_user_agree_when_assume_yes_argument_passed_returns_true_and_does_not_print_prompt(
64-
mocker, context_with_assume_yes, capsys
64+
context_with_assume_yes, capsys
6565
):
6666
result = does_user_agree("Test Prompt")
6767
output = capsys.readouterr()

0 commit comments

Comments
 (0)