Repository navigation
Expand file tree
/
Copy pathtest_secrets.py
More file actions
154 lines (116 loc) · 5.72 KB
/
Copy pathtest_secrets.py
File metadata and controls
154 lines (116 loc) · 5.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from types import SimpleNamespace
import pytest
from buffdata.engine import secrets
from buffdata.engine.client import GeminiClient
from buffdata.engine.secrets import (
AWSSecretsManagerResolver,
AzureKeyVaultResolver,
EnvSecretResolver,
GCPSecretManagerResolver,
VaultSecretResolver,
create_secret_resolver,
get_default_secret_resolver,
set_default_secret_resolver,
)
@pytest.fixture(autouse=True)
def _reset_default_resolver():
# get_default_secret_resolver() memoizes into a process-wide global the first time
# it's ever called, so every test here must reset it before and after or an earlier
# test's backend choice silently leaks into a later one.
set_default_secret_resolver(None)
yield
set_default_secret_resolver(None)
def test_env_resolver_reads_os_environ(monkeypatch):
monkeypatch.setenv("SOME_TEST_KEY", "value-from-env")
resolver = EnvSecretResolver()
assert resolver.get("SOME_TEST_KEY") == "value-from-env"
assert resolver.get("SOME_MISSING_KEY") is None
def test_create_secret_resolver_defaults_to_env(monkeypatch):
monkeypatch.delenv("BUFFDATA_SECRET_BACKEND", raising=False)
assert isinstance(create_secret_resolver(), EnvSecretResolver)
def test_create_secret_resolver_selects_by_name():
assert isinstance(create_secret_resolver("vault"), VaultSecretResolver)
assert isinstance(create_secret_resolver("aws_secrets_manager"), AWSSecretsManagerResolver)
assert isinstance(create_secret_resolver("gcp_secret_manager"), GCPSecretManagerResolver)
assert isinstance(create_secret_resolver("azure_key_vault"), AzureKeyVaultResolver)
def test_create_secret_resolver_rejects_unknown_backend():
with pytest.raises(ValueError, match="Unknown secret backend"):
create_secret_resolver("not-a-real-backend")
def test_get_default_secret_resolver_honors_env_var(monkeypatch):
monkeypatch.setenv("BUFFDATA_SECRET_BACKEND", "vault")
assert isinstance(get_default_secret_resolver(), VaultSecretResolver)
def test_get_default_secret_resolver_is_memoized(monkeypatch):
monkeypatch.setenv("BUFFDATA_SECRET_BACKEND", "vault")
first = get_default_secret_resolver()
monkeypatch.setenv("BUFFDATA_SECRET_BACKEND", "aws_secrets_manager")
second = get_default_secret_resolver()
assert first is second # env var change after the first call has no effect until reset
def test_vault_resolver_reads_kv_v2_fields():
resolver = VaultSecretResolver(path="buffdata", mount_point="secret", url="https://vault.internal", token="t")
resolver._client = SimpleNamespace(
secrets=SimpleNamespace(
kv=SimpleNamespace(
v2=SimpleNamespace(
read_secret_version=lambda path, mount_point: {
"data": {"data": {"GEMINI_API_KEY": "vault-gemini-key"}}
}
)
)
)
)
assert resolver.get("GEMINI_API_KEY") == "vault-gemini-key"
assert resolver.get("MISSING_KEY") is None
def test_vault_resolver_caches_after_first_read():
calls = {"n": 0}
def read_secret_version(path, mount_point):
calls["n"] += 1
return {"data": {"data": {"K": "v"}}}
resolver = VaultSecretResolver(url="https://vault.internal", token="t")
resolver._client = SimpleNamespace(
secrets=SimpleNamespace(kv=SimpleNamespace(v2=SimpleNamespace(read_secret_version=read_secret_version)))
)
resolver.get("K")
resolver.get("K")
assert calls["n"] == 1
def test_aws_secrets_manager_resolver_parses_json_secret():
resolver = AWSSecretsManagerResolver(secret_id="buffdata/keys")
resolver._cache = {"ANTHROPIC_API_KEY": "aws-anthropic-key"}
assert resolver.get("ANTHROPIC_API_KEY") == "aws-anthropic-key"
assert resolver.get("MISSING") is None
def test_gcp_secret_manager_resolver_accesses_latest_version():
resolver = GCPSecretManagerResolver(project_id="my-project")
resolver._client = SimpleNamespace(
access_secret_version=lambda name: SimpleNamespace(
payload=SimpleNamespace(data=b"gcp-openai-key")
)
)
assert resolver.get("OPENAI_API_KEY") == "gcp-openai-key"
# second call for the same key must not hit the fake client again (would raise if it did,
# since we only stub one call's worth of behavior implicitly via caching)
assert resolver.get("OPENAI_API_KEY") == "gcp-openai-key"
def test_gcp_secret_manager_resolver_returns_none_on_lookup_failure():
resolver = GCPSecretManagerResolver(project_id="my-project")
def _raise(name):
raise RuntimeError("not found")
resolver._client = SimpleNamespace(access_secret_version=_raise)
assert resolver.get("MISSING") is None
def test_azure_key_vault_resolver_normalizes_secret_names():
seen_names = []
def get_secret(name):
seen_names.append(name)
return SimpleNamespace(value="azure-secret-value")
resolver = AzureKeyVaultResolver(vault_url="https://example.vault.azure.net")
resolver._client = SimpleNamespace(get_secret=get_secret)
assert resolver.get("AZURE_OPENAI_API_KEY") == "azure-secret-value"
assert seen_names == ["azure-openai-api-key"]
def test_client_picks_up_key_from_injected_resolver(monkeypatch):
# End-to-end: a client's api_key resolution should go through whatever resolver is
# configured, without the client class knowing anything about the backend.
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
class FakeResolver:
def get(self, key):
return "resolved-from-fake-backend" if key == "GEMINI_API_KEY" else None
set_default_secret_resolver(FakeResolver())
client = GeminiClient()
assert client.api_key == "resolved-from-fake-backend"