From 646e8315c1c9e7b435c9abe43dd01febd41a476d Mon Sep 17 00:00:00 2001 From: Shohei KAMON Date: Sat, 19 Apr 2025 16:29:00 +0800 Subject: [PATCH] add configure list and test Signed-off-by: Shohei KAMON --- README.md | 2 +- REUSE.toml | 7 ++- fireblocks_cli/commands/configure.py | 45 ++++++++++++++++ poetry.lock.license | 5 ++ tests/test_configure_list.py | 78 ++++++++++++++++++++++++++++ 5 files changed, 135 insertions(+), 2 deletions(-) create mode 100644 poetry.lock.license create mode 100644 tests/test_configure_list.py diff --git a/README.md b/README.md index 6141957..8344ce2 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ fireblocks-cli --help |-------------------|-------------|--------------|----------------------------------------------------------------|-----------------------------------------------------------------------| | `init` | ✅ | ✅ | Initialize the default configuration files | Creates `~/.config/fireblocks-cli/config.toml` and `~/.config/fireblocks-cli/keys/` | | `gen-keys` | ✅ | ✅ | Generate Fireblocks-compatible private key and CSR | Outputs to `.config/fireblocks-cli/keys/{name}.csr`, etc. | -| `list` | n/a | n/a | List all configured profiles | Displays `[profile]` sections from `config.toml` | +| `list` | ✅ | ✅ | List all configured profiles | Displays `[profile]` sections from `config.toml` | | `edit` | ✅ | ✅ | Open the config file in your default `$EDITOR` | Falls back to `vi` or `nano` if `$EDITOR` is not set | | `validate` | ✅ | n/a | Validate the structure and contents of the config file | Checks for invalid or missing keys and values | | `add` | n/a | n/a | Append a new profile to the configuration file | Will add to the bottom of the file without auto-formatting | diff --git a/REUSE.toml b/REUSE.toml index fecebff..991a623 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -1,4 +1,9 @@ -version = 1 +# SPDX-FileCopyrightText: 2025 Ethersecurity Inc. +# +# SPDX-License-Identifier: MPL-2.0 +# Author: Shohei KAMON + +version = 5 [reuse] license = "MPL-2.0" copyright = "Ethersecurity" diff --git a/fireblocks_cli/commands/configure.py b/fireblocks_cli/commands/configure.py index d88bb07..ca49ce0 100644 --- a/fireblocks_cli/commands/configure.py +++ b/fireblocks_cli/commands/configure.py @@ -186,3 +186,48 @@ def edit(): except Exception as e: typer.secho(f"❌ Validation failed: {e}", fg=typer.colors.RED) raise typer.Exit(code=1) + + +@configure_app.command("list") +def list_profiles(): + """ + List available profiles from config.toml and credentials (if present). + Profiles in credentials override those in config.toml. + """ + import toml + from fireblocks_cli.config import get_config_file, get_credentials_file + + config_path = get_config_file() + credentials_path = get_credentials_file() + + combined_data = {} + + # Step 1: load config.toml + if config_path.exists(): + try: + config_data = toml.load(config_path) + combined_data.update(config_data) + except Exception as e: + typer.secho(f"❌ Failed to parse config.toml: {e}", fg=typer.colors.RED) + raise typer.Exit(code=1) + + # Step 2: override with credentials if it exists + if credentials_path.exists(): + try: + credentials_data = toml.load(credentials_path) + combined_data.update(credentials_data) # override same keys + except Exception as e: + typer.secho(f" Failed to parse credentials: {e}", fg=typer.colors.RED) + raise typer.Exit(code=1) + + if not combined_data: + typer.echo("⚠️ No profiles found in config.toml or credentials.") + return + + typer.echo("📜 Available Profiles:\n") + for name, values in combined_data.items(): + api_id = values.get("api_id", "") + secret_type = values.get("api_secret_key", {}).get("type", "") + typer.echo( + f"🔹 [{name}]\n api_id: {api_id}\n secret_type: {secret_type}\n" + ) diff --git a/poetry.lock.license b/poetry.lock.license new file mode 100644 index 0000000..f3fafd4 --- /dev/null +++ b/poetry.lock.license @@ -0,0 +1,5 @@ +SPDX-FileCopyrightText: 2025 2025 Ethersecurity Inc. +SPDX-FileCopyrightText: 2025 Ethersecurity Inc. + +SPDX-License-Identifier: MPL-2.0 +# Author: Shohei KAMON diff --git a/tests/test_configure_list.py b/tests/test_configure_list.py new file mode 100644 index 0000000..155bbe0 --- /dev/null +++ b/tests/test_configure_list.py @@ -0,0 +1,78 @@ +# SPDX-FileCopyrightText: 2025 Ethersecurity Inc. +# +# SPDX-License-Identifier: MPL-2.0 +# Author: Shohei KAMON + +import pytest +from pathlib import Path +from typer.testing import CliRunner +from fireblocks_cli.main import app + +runner = CliRunner() + + +@pytest.fixture +def mock_home(tmp_path, monkeypatch): + """ + Redirect HOME to a temporary directory to isolate config paths. + Creates both config.toml and credentials. + """ + monkeypatch.setattr(Path, "home", lambda: tmp_path) + + config_dir = tmp_path / ".config" / "fireblocks-cli" + config_dir.mkdir(parents=True, exist_ok=True) + + config_toml = config_dir / "config.toml" + credentials = config_dir / "credentials" + + # Write config.toml with default profile (should be overridden) + config_toml.write_text( + """ +[default] +api_id = "from-config" +api_secret_key = { type = "file", value = "config.key" } + +[only_in_config] +api_id = "config-only" +api_secret_key = { type = "file", value = "config.key" } +""" + ) + + # Write credentials with default profile (should override) and another unique one + credentials.write_text( + """ +[default] +api_id = "from-credentials" +api_secret_key = { type = "vault", value = "vault.key" } + +[only_in_credentials] +api_id = "credentials-only" +api_secret_key = { type = "vault", value = "vault.key" } +""" + ) + + return config_toml, credentials + + +def test_configure_list_merges_profiles(mock_home): + """ + Test that `configure list` merges config.toml and credentials, + and credentials override config values when profile names match. + """ + result = runner.invoke(app, ["configure", "list"]) + output = result.stdout + + assert result.exit_code == 0 + + # default should come from credentials (vault) + assert "[default]" in output + assert "api_id: from-credentials" in output + assert "secret_type: vault" in output + + # config-only profile should still appear + assert "[only_in_config]" in output + assert "api_id: config-only" in output + + # credentials-only profile should also appear + assert "[only_in_credentials]" in output + assert "api_id: credentials-only" in output