From 113ed48986315d2fd48e6f0888edfec57e2e0e4e Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Mon, 17 Jul 2023 16:23:14 -0700 Subject: [PATCH] dclient auth remove command, plus tests --- dclient/cli.py | 28 +++++++++++++++++++++------- tests/test_cli_auth.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 7 deletions(-) create mode 100644 tests/test_cli_auth.py diff --git a/dclient/cli.py b/dclient/cli.py index be1f483..ac660f4 100644 --- a/dclient/cli.py +++ b/dclient/cli.py @@ -95,10 +95,10 @@ def list_(_json): click.echo(f"{alias} = {url}") -@alias.command() +@alias.command(name="add") @click.argument("name") @click.argument("url") -def add(name, url): +def alias_add(name, url): "Add an alias" config_dir = get_config_dir() config_dir.mkdir(parents=True, exist_ok=True) @@ -108,9 +108,9 @@ def add(name, url): aliases_file.write_text(json.dumps(aliases, indent=4)) -@alias.command() +@alias.command(name="remove") @click.argument("name") -def remove(name): +def alias_remove(name): "Remove an alias" config_dir = get_config_dir() aliases_file = config_dir / "aliases.json" @@ -127,10 +127,10 @@ def auth(): "Manage authentication for different instances" -@auth.command() +@auth.command(name="add") @click.argument("alias_or_url") @click.option("--token", prompt=True, hide_input=True) -def add(alias_or_url, token): +def auth_add(alias_or_url, token): """ Add an authentication token for an alias or URL @@ -155,7 +155,7 @@ def add(alias_or_url, token): @auth.command(name="list") -def list_(): +def auth_list(): "List stored API tokens" auths_file = get_config_dir() / "auth.json" click.echo("Tokens file: {}".format(auths_file)) @@ -166,6 +166,20 @@ def list_(): click.echo("{}:\t{}..".format(url, token[:1])) +@auth.command(name="remove") +@click.argument("alias_or_url") +def auth_remove(alias_or_url): + "Remove the API token for an alias or URL" + config_dir = get_config_dir() + auth_file = config_dir / "auth.json" + auths = _load_auths(auth_file) + try: + del auths[alias_or_url] + auth_file.write_text(json.dumps(auths, indent=4)) + except KeyError: + raise click.ClickException("No such URL or alias") + + def _load_aliases(aliases_file): if aliases_file.exists(): aliases = json.loads(aliases_file.read_text()) diff --git a/tests/test_cli_auth.py b/tests/test_cli_auth.py new file mode 100644 index 0000000..ba30e22 --- /dev/null +++ b/tests/test_cli_auth.py @@ -0,0 +1,41 @@ +from click.testing import CliRunner +from dclient.cli import cli +import pathlib +import json + + +def test_auth(mocker, tmpdir): + mocker.patch("dclient.cli.get_config_dir", return_value=pathlib.Path(tmpdir)) + runner = CliRunner() + result = runner.invoke(cli, ["auth", "list"]) + assert result.exit_code == 0 + assert result.output.startswith("Tokens file:") + # Should only have one line + assert len([line for line in result.output.split("\n") if line.strip()]) == 1 + + # Now add a token + result2 = runner.invoke(cli, ["auth", "add", "https://example.com"], input="xyz\n") + assert result2.exit_code == 0 + + # Check the tokens file + auth_file = pathlib.Path(tmpdir) / "auth.json" + assert json.loads(auth_file.read_text()) == {"https://example.com": "xyz"} + + # auth list should show that now + result3 = runner.invoke(cli, ["auth", "list"]) + assert result3.output.startswith("Tokens file:") + assert "https://example.com" in result3.output + + # Remove should fail with an incorrect URL + result4 = runner.invoke(cli, ["auth", "remove", "https://example.com/foo"]) + assert result4.exit_code == 1 + assert result4.output == "Error: No such URL or alias\n" + + # Remove should work with the correct URL + result5 = runner.invoke(cli, ["auth", "remove", "https://example.com"]) + assert result5.exit_code == 0 + assert result5.output == "" + + # Check the tokens file + auth_file = pathlib.Path(tmpdir) / "auth.json" + assert json.loads(auth_file.read_text()) == {}