fix(shared-ai): implementar aislamiento de credenciales locales y globales
This commit is contained in:
@@ -160,6 +160,16 @@ class configfile:
|
||||
# Deep merge: shared as base, user overrides
|
||||
base = copy.deepcopy(self._shared_config.config.get(key, {}))
|
||||
if isinstance(base, dict) and isinstance(val, dict):
|
||||
# Credential isolation:
|
||||
# If user defines engineer credentials, discard shared ones
|
||||
if "engineer_api_key" in val or "engineer_auth" in val:
|
||||
base.pop("engineer_api_key", None)
|
||||
base.pop("engineer_auth", None)
|
||||
# If user defines architect credentials, discard shared ones
|
||||
if "architect_api_key" in val or "architect_auth" in val:
|
||||
base.pop("architect_api_key", None)
|
||||
base.pop("architect_auth", None)
|
||||
|
||||
# Recursive update for inner dictionaries (like mcp_servers or model details)
|
||||
def deep_merge(d1, d2):
|
||||
for k, v in d2.items():
|
||||
|
||||
@@ -160,3 +160,58 @@ def test_registry_injection_and_hot_reload(temp_config_dir):
|
||||
ai_settings_updated = provider2.config.get_effective_setting("ai")
|
||||
assert ai_settings_updated.get("engineer_api_key") == "global-updated-key"
|
||||
assert ai_settings_updated.get("engineer_model") == "global-model"
|
||||
|
||||
|
||||
def test_shared_ai_credential_isolation(temp_config_dir):
|
||||
"""Test that setting user engineer/architect credentials discards corresponding shared credentials."""
|
||||
shared_dir = os.path.join(temp_config_dir, "shared_isolation")
|
||||
user_dir = os.path.join(temp_config_dir, "user_isolation")
|
||||
os.makedirs(shared_dir, exist_ok=True)
|
||||
os.makedirs(user_dir, exist_ok=True)
|
||||
|
||||
shared_path = os.path.join(shared_dir, "config.yaml")
|
||||
user_path = os.path.join(user_dir, "config.yaml")
|
||||
|
||||
# Shared has both api_key and auth
|
||||
shared_data = {
|
||||
"config": {
|
||||
"ai": {
|
||||
"engineer_api_key": "global-initial-key",
|
||||
"engineer_auth": {"vertex_project": "shared-project", "api_key": "shared-auth-key"},
|
||||
"architect_api_key": "global-arch-key",
|
||||
"architect_auth": {"project": "arch-project"}
|
||||
}
|
||||
},
|
||||
"connections": {},
|
||||
"profiles": {}
|
||||
}
|
||||
with open(shared_path, "w") as f:
|
||||
yaml.safe_dump(shared_data, f)
|
||||
|
||||
# User configures ONLY engineer_api_key (expects engineer_auth to be discarded)
|
||||
# and ONLY architect_auth (expects architect_api_key to be discarded)
|
||||
user_data = {
|
||||
"config": {
|
||||
"ai": {
|
||||
"engineer_api_key": "user-custom-key",
|
||||
"architect_auth": {"project": "user-project", "api_key": "user-auth-key"}
|
||||
}
|
||||
},
|
||||
"connections": {},
|
||||
"profiles": {}
|
||||
}
|
||||
with open(user_path, "w") as f:
|
||||
yaml.safe_dump(user_data, f)
|
||||
|
||||
shared_config = configfile(conf=shared_path)
|
||||
user_config = configfile(conf=user_path, shared_config=shared_config)
|
||||
|
||||
effective_ai = user_config.get_effective_setting("ai")
|
||||
|
||||
# 1. Engineer: local api_key is present, so shared engineer_auth must be completely discarded
|
||||
assert effective_ai.get("engineer_api_key") == "user-custom-key"
|
||||
assert "engineer_auth" not in effective_ai
|
||||
|
||||
# 2. Architect: local auth is present, so shared architect_api_key must be completely discarded
|
||||
assert effective_ai.get("architect_auth") == {"project": "user-project", "api_key": "user-auth-key"}
|
||||
assert "architect_api_key" not in effective_ai
|
||||
|
||||
Reference in New Issue
Block a user