emma-hermes/tests/test_env_loader_secret_sour...

120 lines
3.8 KiB
Python

"""Tests for the secret-source tracking in ``hermes_cli.env_loader``.
These cover the small public surface that lets `hermes model` / `hermes setup`
label detected credentials with their origin ("from Bitwarden") so users
don't see an unexplained "credentials ✓" line when their .env is empty.
"""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from hermes_cli import env_loader # noqa: E402
@pytest.fixture(autouse=True)
def _reset_sources():
"""Each test starts with a clean source map."""
env_loader._SECRET_SOURCES.clear()
yield
env_loader._SECRET_SOURCES.clear()
def test_get_secret_source_returns_none_for_untracked_var():
assert env_loader.get_secret_source("ANTHROPIC_API_KEY") is None
def test_get_secret_source_returns_label_for_tracked_var():
env_loader._SECRET_SOURCES["ANTHROPIC_API_KEY"] = "bitwarden"
assert env_loader.get_secret_source("ANTHROPIC_API_KEY") == "bitwarden"
def test_format_secret_source_suffix_empty_for_untracked():
# Credentials from .env or the shell shouldn't add noise — the
# implicit case stays unlabeled.
assert env_loader.format_secret_source_suffix("ANTHROPIC_API_KEY") == ""
def test_format_secret_source_suffix_bitwarden_uses_proper_name():
env_loader._SECRET_SOURCES["ANTHROPIC_API_KEY"] = "bitwarden"
assert (
env_loader.format_secret_source_suffix("ANTHROPIC_API_KEY")
== " (from Bitwarden)"
)
def test_format_secret_source_suffix_generic_label_for_future_sources():
# Future-proofing: a new secret source (e.g. "vault") should still
# produce a sensible label without needing to edit every call site.
env_loader._SECRET_SOURCES["OPENAI_API_KEY"] = "vault"
assert (
env_loader.format_secret_source_suffix("OPENAI_API_KEY")
== " (from vault)"
)
def test_apply_external_secret_sources_records_bitwarden_origin(tmp_path, monkeypatch):
"""End-to-end: when ``apply_bitwarden_secrets`` returns applied keys,
they end up in ``_SECRET_SOURCES`` so the UI can label them."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
config_path = tmp_path / "config.yaml"
config_path.write_text(
"secrets:\n"
" bitwarden:\n"
" enabled: true\n"
" project_id: test-project\n"
" access_token_env: BWS_ACCESS_TOKEN\n",
encoding="utf-8",
)
# Stub apply_bitwarden_secrets to return a synthetic FetchResult.
from agent.secret_sources.bitwarden import FetchResult
fake_result = FetchResult(
secrets={"ANTHROPIC_API_KEY": "sk-ant-test"},
applied=["ANTHROPIC_API_KEY"],
)
def _fake_apply(**_kwargs):
return fake_result
# The import inside _apply_external_secret_sources is lazy, so we
# patch the *module attribute* it will pull in.
import agent.secret_sources.bitwarden as bw_module
monkeypatch.setattr(bw_module, "apply_bitwarden_secrets", _fake_apply)
env_loader._apply_external_secret_sources(tmp_path)
assert env_loader.get_secret_source("ANTHROPIC_API_KEY") == "bitwarden"
assert (
env_loader.format_secret_source_suffix("ANTHROPIC_API_KEY")
== " (from Bitwarden)"
)
def test_apply_external_secret_sources_noop_when_disabled(tmp_path, monkeypatch):
"""Disabled Bitwarden config must not touch the source map."""
monkeypatch.setenv("HERMES_HOME", str(tmp_path))
config_path = tmp_path / "config.yaml"
config_path.write_text(
"secrets:\n"
" bitwarden:\n"
" enabled: false\n",
encoding="utf-8",
)
env_loader._apply_external_secret_sources(tmp_path)
assert env_loader.get_secret_source("ANTHROPIC_API_KEY") is None