Skip to content

Commit

Permalink
feat(groups): command lists existing groups
Browse files Browse the repository at this point in the history
optionally filter by a term matching group ID or label

support subcommands with additional args
  • Loading branch information
thekaveman committed Sep 5, 2023
1 parent 7f7b482 commit e6525b3
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 17 deletions.
27 changes: 27 additions & 0 deletions littlepay/commands/groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from littlepay.api.client import Client
from littlepay.commands import RESULT_SUCCESS
from littlepay.config import Config


def groups(group_term: str = None, group_command: str = None, **kwargs) -> int:
config = Config()
client = Client.from_active_config(config)
client.oauth.ensure_active_token(client.token)
config.active_token = client.token

groups = client.get_concession_groups()

if group_term is not None:
term = group_term.lower()
groups = filter(
lambda g: term in g.id.lower() or term in g.label.lower(),
groups,
)

groups = list(groups)
print(f"👥 Matching groups ({len(groups)})")

for group in groups:
print(group)

return RESULT_SUCCESS
52 changes: 35 additions & 17 deletions littlepay/main.py
Original file line number Diff line number Diff line change
@@ -1,55 +1,73 @@
from argparse import ArgumentParser
from argparse import _SubParsersAction, ArgumentParser
import sys

from littlepay import __version__ as version
from littlepay.commands.configure import configure
from littlepay.commands.groups import groups
from littlepay.commands.switch import switch
from littlepay.config import CONFIG_TYPES, Config


def _subcmd(subparsers: _SubParsersAction, name: str, help: str) -> ArgumentParser:
"""Helper creates a new subcommand parser in the collection."""
parser = subparsers.add_parser(name, help=help)
return parser


def main(argv=None):
argv = argv if argv is not None else sys.argv[1:]
parser = ArgumentParser(prog="littlepay")
main_parser = ArgumentParser(prog="littlepay")

# https://stackoverflow.com/a/8521644/812183
parser.add_argument(
# littlepay -v
# littlepay --version
main_parser.add_argument(
"-v",
"--version",
action="version",
version=f"%(prog)s {version}",
)
parser.add_argument(
# littlepay -c CONFIG_PATH
# littlepay --config CONFIG_PATH
main_parser.add_argument(
"-c",
"--config",
dest="config_path",
help="Path to a readable and writeable config file to use. File will be created if it does not exist.",
)

subparsers = parser.add_subparsers(dest="command")
main_commands = main_parser.add_subparsers(dest="command")

def _subcmd(name, help) -> ArgumentParser:
"""Helper creates a new subcommand parser."""
parser = subparsers.add_parser(name, help=help)
return parser
def _maincmd(name, help):
return _subcmd(main_commands, name, help)

config_parser = _subcmd("config", help="Get or set configuration.")
config_parser.add_argument(
"config_path",
nargs="?",
default=Config.current_path(),
)
# littlepay config [CONFIG_PATH]
config_parser = _maincmd("config", help="Get or set configuration.")
config_parser.add_argument("config_path", nargs="?", default=Config.current_path())

# littlepay groups [-f GROUP]
groups_parser = _maincmd("groups", help="Get information about groups in the active environment.")
groups_parser.add_argument("-f", "--filter", help="Filter for groups with matching group ID or label", dest="group")
_ = groups_parser.add_subparsers(dest="group_command", required=False)

switch_parser = _subcmd("switch", help="Switch the active environment or participant.")
# littlepay switch {env, participant} VALUE
switch_parser = _maincmd("switch", help="Switch the active environment or participant.")
switch_parser.add_argument("switch_type", choices=CONFIG_TYPES, help="The type of object to switch", metavar="TYPE")
switch_parser.add_argument("switch_arg", help="The new object value", metavar="VALUE")

if len(argv) == 0:
argv = ["config"]

args = parser.parse_args(argv)
args = main_parser.parse_args(argv)

if args.command == "config" or args.config_path:
return configure(args.config_path)
elif args.command == "groups":
return groups(
getattr(args, "group", None),
getattr(args, "group_command", None),
funding_source_id=getattr(args, "funding_source_id", None),
)
elif args.command == "switch":
return switch(args.switch_type, args.switch_arg)

Expand Down
74 changes: 74 additions & 0 deletions tests/commands/test_groups.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from argparse import Namespace
import pytest

from littlepay.api.groups import GroupResponse
from littlepay.commands import RESULT_SUCCESS
from littlepay.commands.groups import groups

GROUP_RESPONSES = [
GroupResponse("id0", "zero", "participant123"),
GroupResponse("id1", "one", "participant123"),
GroupResponse("id2", "two", "participant123"),
]


@pytest.fixture(autouse=True)
def mock_config(mocker):
mocker.patch("littlepay.commands.groups.config")


@pytest.fixture
def mock_client(mocker):
client = mocker.Mock()
mocker.patch("littlepay.commands.groups.Client.from_active_config", return_value=client)
return client


@pytest.fixture(autouse=True)
def mock_get_groups(mock_client):
mock_client.get_concession_groups.return_value = GROUP_RESPONSES


def test_groups_default(mock_client, capfd):
res = groups()
capture = capfd.readouterr()

assert res == RESULT_SUCCESS

mock_client.oauth.ensure_active_token.assert_called_once()

assert "Matching groups (3)" in capture.out
for response in GROUP_RESPONSES:
assert str(response) in capture.out


@pytest.mark.parametrize("group_response", GROUP_RESPONSES)
def test_groups_group_term__group_id(group_response, capfd):
args = Namespace(group_term=group_response.id)
res = groups(args)
capture = capfd.readouterr()

assert res == RESULT_SUCCESS

assert "Matching groups (1)" in capture.out
for response in GROUP_RESPONSES:
if response == group_response:
assert str(response) in capture.out
else:
assert str(response) not in capture.out


@pytest.mark.parametrize("group_response", GROUP_RESPONSES)
def test_groups_group_term__group_label(group_response, capfd):
args = Namespace(group_term=group_response.label)
res = groups(args)
capture = capfd.readouterr()

assert res == RESULT_SUCCESS

assert "Matching groups (1)" in capture.out
for response in GROUP_RESPONSES:
if response == group_response:
assert str(response) in capture.out
else:
assert str(response) not in capture.out
6 changes: 6 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ def mock_commands_config(mock_module_name):
return mock_module_name("configure")


@pytest.fixture
def mock_commands_groups(mock_module_name):
"""Fixture returns a function that patches commands.groups in a given module."""
return mock_module_name("groups")


@pytest.fixture
def mock_commands_switch(mock_module_name):
"""Fixture returns a function that patches commands.switch in a given module."""
Expand Down
21 changes: 21 additions & 0 deletions tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ def mock_commands_config(mock_commands_config):
return mock_commands_config(MODULE)


@pytest.fixture
def mock_commands_groups(mock_commands_groups):
return mock_commands_groups(MODULE)


@pytest.fixture
def mock_commands_switch(mock_commands_switch):
return mock_commands_switch(MODULE)
Expand Down Expand Up @@ -54,6 +59,22 @@ def test_main_config_config_path(custom_config_file: Path, mock_commands_config)
mock_commands_config.assert_called_once_with(new_config_path)


def test_main_groups(mock_commands_groups):
result = main(argv=["groups"])

assert result == RESULT_SUCCESS
mock_commands_groups.assert_called_once()


@pytest.mark.parametrize("filter_flag", ["-f", "--filter"])
def test_main_groups_filter(mock_commands_groups, filter_flag):
result = main(argv=["groups", filter_flag, "term"])

assert result == RESULT_SUCCESS
mock_commands_groups.assert_called_once()
assert "term" in mock_commands_groups.call_args.args


@pytest.mark.parametrize("switch_type", CONFIG_TYPES)
def test_main_switch_recognized_type(mock_commands_switch, switch_type):
result = main(argv=["switch", switch_type, "new_value"])
Expand Down

0 comments on commit e6525b3

Please sign in to comment.