Skip to content

Commit 45755f0

Browse files
committed
chore: changelog updated
1 parent 15842de commit 45755f0

File tree

5 files changed

+140
-7
lines changed

5 files changed

+140
-7
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
All notable changes to this project will be documented in this file.
44

5+
## [0.1.0] - 2025-01-09
6+
7+
### 🚀 Features
8+
9+
- Added option to pass credentials as dict object
10+
511
## [0.0.2] - 2025-01-02
612

713
### 🚀 Features
@@ -29,6 +35,7 @@ All notable changes to this project will be documented in this file.
2935
- Version updated to 0.0.2
3036
- Changelog updated
3137
- Changelog updated
38+
- Changelog updated
3239

3340
## [0.0.1] - 2024-12-27
3441

src/web3_google_hsm/accounts/gcp_kms_account.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
from eth_account.messages import _hash_eip191_message, encode_defunct # noqa: PLC2701
99
from eth_typing import ChecksumAddress
1010
from eth_utils import keccak, to_checksum_address
11+
from google.auth import load_credentials_from_dict
1112
from google.cloud import kms
1213
from google.protobuf import duration_pb2 # type: ignore
1314
from pydantic import BaseModel, Field, PrivateAttr
1415
from rich.traceback import install
1516

1617
from web3_google_hsm.config import BaseConfig
17-
from web3_google_hsm.exceptions import ConfigurationError, SignatureError
18+
from web3_google_hsm.exceptions import SignatureError
1819
from web3_google_hsm.types.ethereum_types import MSG_HASH_LENGTH, Signature, Transaction
1920
from web3_google_hsm.utils import convert_der_to_rsv, extract_public_key_bytes
2021

@@ -46,15 +47,14 @@ def __init__(self, config: BaseConfig | None = None, credentials: dict | None =
4647
Raises:
4748
ValueError: If both config and credentials are provided
4849
"""
49-
if config is not None and credentials is not None:
50-
msg = "Cannot provide both config and credentials. Use one or neither."
51-
raise ConfigurationError(msg)
5250

5351
super().__init__(**data)
5452

53+
if isinstance(credentials, dict):
54+
credentials, _ = load_credentials_from_dict(credentials)
5555
# Initialize client based on provided auth method
5656
self._client = (
57-
kms.KeyManagementServiceClient(credentials=credentials) if credentials else kms.KeyManagementServiceClient()
57+
kms.KeyManagementServiceClient(credentials=credentials) if credentials else kms.KeyManagementServiceClient() # type: ignore
5858
)
5959

6060
# Initialize settings if config is provided, otherwise None
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import os
2+
3+
from pydantic import ValidationError
4+
import pytest
5+
from web3_google_hsm.accounts.gcp_kms_account import GCPKmsAccount
6+
from web3_google_hsm.config import BaseConfig
7+
import json
8+
9+
# Define required environment variables
10+
REQUIRED_ENV_VARS = {
11+
"GOOGLE_CLOUD_PROJECT": os.getenv("GOOGLE_CLOUD_PROJECT"),
12+
"GOOGLE_CLOUD_REGION": os.getenv("GOOGLE_CLOUD_REGION"),
13+
"KEY_RING": os.getenv("KEY_RING"),
14+
"KEY_NAME": os.getenv("KEY_NAME"),
15+
"GCP_CREDENTIALS_STRING": os.getenv("GCP_CREDENTIALS_STRING"),
16+
"GCP_CREDENTIALS_STRING": os.getenv("GCP_CREDENTIALS_STRING"),
17+
}
18+
19+
# Skip all tests if any required env var is missing
20+
missing_vars = [k for k, v in REQUIRED_ENV_VARS.items() if not v]
21+
pytestmark = pytest.mark.skipif(
22+
bool(missing_vars),
23+
reason=f"Missing required environment variables: {', '.join(missing_vars)}"
24+
)
25+
26+
def test_account_initialization_with_both():
27+
"""Test initializing account with both config and credentials."""
28+
# Load credentials from GCP_CREDENTIALS_STRING env var
29+
credentials = json.loads(os.environ["GCP_CREDENTIALS_STRING"])
30+
31+
# Create config from environment
32+
config = BaseConfig.from_env()
33+
34+
# Initialize account with both
35+
account = GCPKmsAccount(config=config, credentials=credentials)
36+
37+
# Verify initialization
38+
assert account._client is not None
39+
assert account._settings is not None
40+
assert account.key_path is not None
41+
assert account.address.startswith("0x")
42+
43+
# Test basic functionality
44+
message = "Test message"
45+
signature = account.sign_message(message)
46+
assert signature.v in (27, 28)
47+
assert len(signature.r) == 32
48+
assert len(signature.s) == 32
49+
50+
def test_account_initialization_with_neither():
51+
"""Test initializing account with neither config nor credentials (using env vars)."""
52+
# Initialize account without explicit config or credentials
53+
account = GCPKmsAccount()
54+
55+
# Verify initialization
56+
assert account._client is not None
57+
assert account._settings is not None # Should be created from env
58+
assert account.key_path is not None
59+
assert account.address.startswith("0x")
60+
61+
# Test basic functionality
62+
message = "Test message"
63+
signature = account.sign_message(message)
64+
assert signature.v in (27, 28)
65+
assert len(signature.r) == 32
66+
assert len(signature.s) == 32
67+
68+
def test_fail_account_initialization_with_only_config(monkeypatch):
69+
"""Test that initializing with only config raises error."""
70+
env_vars_to_clear = [
71+
"GOOGLE_CLOUD_PROJECT",
72+
"GOOGLE_CLOUD_REGION",
73+
"KEY_RING",
74+
"KEY_NAME",
75+
"GOOGLE_APPLICATION_CREDENTIALS",
76+
"GCP_CREDENTIALS_STRING"
77+
]
78+
79+
for env_var in env_vars_to_clear:
80+
monkeypatch.delenv(env_var, raising=False)
81+
82+
with pytest.raises(ValidationError):
83+
config = BaseConfig.from_env()
84+
GCPKmsAccount(config=config)
85+
86+
def test_fail_account_initialization_with_only_credentials(monkeypatch):
87+
"""Test that initializing with only credentials raises error."""
88+
env_vars_to_clear = [
89+
"GOOGLE_CLOUD_PROJECT",
90+
"GOOGLE_CLOUD_REGION",
91+
"KEY_RING",
92+
"KEY_NAME",
93+
]
94+
95+
for env_var in env_vars_to_clear:
96+
monkeypatch.delenv(env_var, raising=False)
97+
credentials = json.loads(os.environ["GCP_CREDENTIALS_STRING"])
98+
99+
with pytest.raises(ValidationError):
100+
GCPKmsAccount(credentials=credentials)
101+
102+
def test_key_path_matches_config(monkeypatch):
103+
"""Test that the key path matches the config values."""
104+
# Load both config and credentials
105+
credentials = json.loads(os.environ["GCP_CREDENTIALS_STRING"])
106+
107+
config = BaseConfig.from_env()
108+
account = GCPKmsAccount(config=config, credentials=credentials)
109+
110+
# Verify key path contains all the expected components
111+
assert config.project_id in account.key_path
112+
assert config.location_id in account.key_path
113+
assert config.key_ring_id in account.key_path
114+
assert config.key_id in account.key_path

tests/integration/accounts/test_gcp_kms_account_integration.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def test_transaction_signing(gcp_account, fund_account, web3):
6262
gas_price=web3.eth.gas_price,
6363
gas_limit=21000,
6464
to="0xa5D3241A1591061F2a4bB69CA0215F66520E67cf",
65-
value=web3.to_wei(0.001, "ether"),
65+
value=web3.to_wei(0.0001, "ether"),
6666
data="0x",
6767
from_=gcp_account.address
6868
)

tests/unit/test_cli.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,20 @@ def test_generate_with_explicit_args(
122122
assert result.exit_code == 0
123123
assert "Created Ethereum signing key" in result.stdout
124124

125-
def test_fail_generate_missing_required_args(self, runner: CliRunner) -> None:
125+
def test_fail_generate_missing_required_args(self, runner: CliRunner, monkeypatch) -> None:
126126
"""Test key generation fails when required arguments are missing."""
127+
# ? Need to clrear the env vars in order to raise error in the cli
128+
env_vars_to_clear = [
129+
"GOOGLE_CLOUD_PROJECT",
130+
"GOOGLE_CLOUD_REGION",
131+
"KEY_RING",
132+
"KEY_NAME",
133+
"GOOGLE_APPLICATION_CREDENTIALS",
134+
"GCP_CREDENTIALS_STRING"
135+
]
136+
137+
for env_var in env_vars_to_clear:
138+
monkeypatch.delenv(env_var, raising=False)
127139
result = runner.invoke(app, ["generate"])
128140
assert result.exit_code == 2
129141
assert "Usage:" in result.stdout

0 commit comments

Comments
 (0)