Skip to content
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

New option to save user emails #641

Merged
merged 16 commits into from
Aug 21, 2023
4 changes: 4 additions & 0 deletions SPRINTLOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -284,4 +284,8 @@ _Nothing merged in CLI during this sprint_
# 2023-08-07 - 2023-08-18

- Dependency: Bump `PyYAML` to 6.0.1 due to docker issues ([#642](https://github.com/ScilifelabDataCentre/dds_cli/pull/642))

# 2023-08-21 - 2023-09-01

- Print understandable message when request response doesn't contain json ([#638](https://github.com/ScilifelabDataCentre/dds_cli/pull/638))
- New option in `dds user ls`: `--save-emails` for Super Admins to save emails to file ([#641](https://github.com/ScilifelabDataCentre/dds_cli/pull/641))
1 change: 1 addition & 0 deletions dds_cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ class DDSEndpoint:
USER_ACTIVATION = BASE_ENDPOINT + "/user/activation"
USER_ACTIVATE_TOTP = BASE_ENDPOINT + "/user/totp/activate"
USER_ACTIVATE_HOTP = BASE_ENDPOINT + "/user/hotp/activate"
USER_EMAILS = BASE_ENDPOINT + "/user/emails"

# Authentication - user and project
ENCRYPTED_TOKEN = BASE_ENDPOINT + "/user/encrypted_token"
Expand Down
12 changes: 11 additions & 1 deletion dds_cli/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -543,14 +543,22 @@ def user_group_command(_):
@click.option(
"--invites", required=False, is_flag=True, default=False, help="List all current invitations."
)
@click.option(
"--save-emails",
required=False,
is_flag=True,
default=False,
help="[Super Admins only] Save user emails.",
)
@click.pass_obj
def list_users(click_ctx, unit, invites):
def list_users(click_ctx, unit, invites, save_emails):
"""List Unit Admins and Personnel connected to a specific unit.

\b
Super Admins:
- Required to specify a public unit ID.
- Can list users within all units.
- Can save list of user emails.

\b
Unit Admins / Personnel:
Expand All @@ -564,6 +572,8 @@ def list_users(click_ctx, unit, invites):
) as lister:
if invites:
lister.list_invites(invites=invites)
elif save_emails:
lister.save_emails()
else:
lister.list_users(unit=unit)

Expand Down
34 changes: 34 additions & 0 deletions dds_cli/account_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

# Standard library
import logging
import pathlib

# Installed
import rich.markup
Expand Down Expand Up @@ -274,3 +275,36 @@ def find_user(self, user_to_find: str) -> None:
LOG.info(
"Account exists: [bold]%s[/bold]", "[blue]Yes[/blue]" if exists else "[red]No[/red]"
)

def save_emails(self) -> None:
"""Get user emails and save them to a text file."""
# Get emails from API
response, _ = dds_cli.utils.perform_request(
endpoint=dds_cli.DDSEndpoint.USER_EMAILS,
method="get",
headers=self.token,
error_message="Failed getting user emails from the API.",
)

# Verify that one of the required pieces of info were returned
empty = response.get("empty")
emails = response.get("emails")
if not empty and not emails:
raise dds_cli.exceptions.ApiResponseError(
"No information returned from the API. Could not get user emails."
)

if empty:
LOG.info("There are no user emails to save.")
return

# Get list of emails
emails = response.get("emails")
LOG.debug("Saving emails to file...")

# Save emails to file
email_file: pathlib.Path = pathlib.Path("unit_user_emails.txt")
with email_file.open(mode="w+", encoding="utf-8") as file:
file.write("; ".join(emails))

LOG.info("Saved emails to file: %s", email_file)
99 changes: 99 additions & 0 deletions tests/test_account_manager.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from _pytest.logging import LogCaptureFixture
from pyfakefs.fake_filesystem import FakeFilesystem
from dds_cli import account_manager
from dds_cli import utils
Expand All @@ -6,6 +7,8 @@
from dds_cli import DDSEndpoint
from dds_cli import exceptions
import pytest
import pathlib
import logging


from dds_cli.__main__ import LOG
Expand Down Expand Up @@ -47,3 +50,99 @@ def test_list_users_no_unit_empty_response(fs: FakeFilesystem):
assert "The following information was not returned: ['users', 'keys']" in str(
exc_info.value
)


def test_save_emails_empty_response(fs: FakeFilesystem):
"""No file should be created if nothing is returned."""
# Verify that file doesn't exist
non_existent_file: pathlib.Path = pathlib.Path("unit_user_emails.txt")
assert not fs.exists(file_path=non_existent_file)

# Empty not returned and empty False
for response_json in [{}, {"empty": False}]:
# Create mocker
with Mocker() as mock:
# Create mocked request - real request not executed
mock.get(DDSEndpoint.USER_EMAILS, status_code=200, json=response_json)

with pytest.raises(exceptions.ApiResponseError) as exc_info:
# Create accountmanager needed for access and set token to dict
with account_manager.AccountManager(authenticate=False, no_prompt=True) as acm:
acm.token = {} # required, otherwise none
acm.save_emails() # run save emails

assert "No information returned from the API. Could not get user emails." in str(
exc_info.value
)

# Verify that the file still doesn't exist
assert not fs.exists(file_path=non_existent_file)


def test_save_emails_no_emails(fs: FakeFilesystem, caplog: LogCaptureFixture):
"""No file should be created if nothing is returned."""
# Verify that file doesn't exist
non_existent_file: pathlib.Path = pathlib.Path("unit_user_emails.txt")
assert not fs.exists(file_path=non_existent_file)

# Empty not returned and empty False
response_json = {"empty": True}

# Create mocker
with Mocker() as mock:
# Create mocked request - real request not executed
mock.get(DDSEndpoint.USER_EMAILS, status_code=200, json=response_json)

with caplog.at_level(logging.INFO):
# Create accountmanager needed for access and set token to dict
with account_manager.AccountManager(authenticate=False, no_prompt=True) as acm:
acm.token = {} # required, otherwise none
acm.save_emails() # run save emails

assert (
"dds_cli.account_manager",
logging.INFO,
"There are no user emails to save.",
) in caplog.record_tuples

# Verify that the file still doesn't exist
assert not fs.exists(file_path=non_existent_file)


def test_save_emails_emails_returned(fs: FakeFilesystem, caplog: LogCaptureFixture):
"""No file should be created if nothing is returned."""
# Verify that file doesn't exist
file_to_save: pathlib.Path = pathlib.Path("unit_user_emails.txt")
assert not fs.exists(file_path=file_to_save)

# Empty not returned and empty False
response_json = {"emails": ["emailone", "emailtwo", "emailthree", "emailfour"]}

# Create mocker
with Mocker() as mock:
# Create mocked request - real request not executed
mock.get(DDSEndpoint.USER_EMAILS, status_code=200, json=response_json)

with caplog.at_level(logging.DEBUG):
# Create accountmanager 'needed for access and set token to dict
with account_manager.AccountManager(authenticate=False, no_prompt=True) as acm:
acm.token = {} # required, otherwise none
acm.save_emails() # run save emails

assert (
"dds_cli.account_manager",
logging.DEBUG,
"Saving emails to file...",
) in caplog.record_tuples
assert (
"dds_cli.account_manager",
logging.INFO,
f"Saved emails to file: {file_to_save}",
) in caplog.record_tuples

# Verify that the file still doesn't exist
assert fs.exists(file_path=file_to_save)

# Read file and verify contents
with file_to_save.open(mode="r", encoding="utf-8") as f:
assert f.read() == "emailone; emailtwo; emailthree; emailfour"
Loading