101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
"""Tests for secrets_backend module."""
|
|
|
|
# SPDX-License-Identifier: MIT
|
|
# Copyright (c) 2026 Rug Munch Media LLC
|
|
#
|
|
# Part of Pry - https://git.rugmunch.io/RugMunchMedia/pryscraper
|
|
# Licensed under MIT. See LICENSE.
|
|
from __future__ import annotations
|
|
|
|
import importlib
|
|
import os
|
|
from unittest.mock import patch
|
|
|
|
import secrets_backend
|
|
from secrets_backend import (
|
|
BACKEND_AUTO,
|
|
_normalize_name,
|
|
backend_info,
|
|
get_secret,
|
|
set_secret,
|
|
)
|
|
|
|
|
|
def test_normalize_name_adds_pry_prefix():
|
|
assert _normalize_name("api_key") == "pry/api_key"
|
|
assert _normalize_name("pry/api_key") == "pry/api_key"
|
|
# Names that already have a slash are treated as paths (not re-prefixed)
|
|
assert _normalize_name("PRY/api_key").startswith("pry/")
|
|
|
|
|
|
def test_get_secret_default_when_missing():
|
|
# Use a name that definitely doesn't exist anywhere
|
|
with patch.dict(os.environ, {"PRY_SECRET_BACKEND": "env"}, clear=False):
|
|
value = get_secret("definitely_not_a_real_secret_name_xyz_12345", default="missing")
|
|
assert value == "missing"
|
|
|
|
|
|
def test_get_secret_from_env(monkeypatch):
|
|
monkeypatch.setenv("PRY_SECRET_BACKEND", "env")
|
|
monkeypatch.setenv("PRY_TEST_SECRET", "from-env")
|
|
value = get_secret("test_secret")
|
|
assert value == "from-env"
|
|
|
|
|
|
def test_get_secret_from_env_uppercase(monkeypatch):
|
|
"""Even when the caller passes lowercase, we should find PRY_UPPERCASE."""
|
|
monkeypatch.setenv("PRY_SECRET_BACKEND", "env")
|
|
monkeypatch.setenv("PRY_FOO", "upper-value")
|
|
value = get_secret("foo")
|
|
assert value == "upper-value"
|
|
|
|
|
|
def test_get_secret_falls_back_to_env_when_gopass_misses(monkeypatch):
|
|
"""When the active backend is gopass and a secret is not in gopass,
|
|
we should fall back to the env (so partial migrations work)."""
|
|
monkeypatch.setenv("PRY_SECRET_BACKEND", "gopass")
|
|
monkeypatch.setenv("PRY_FALLBACK_TEST", "env-fallback")
|
|
# gopass lookup will fail (entry doesn't exist), then we check env
|
|
with patch("secrets_backend._get_from_gopass", return_value=None):
|
|
value = get_secret("fallback_test")
|
|
assert value == "env-fallback"
|
|
|
|
|
|
def test_backend_info_returns_expected_keys():
|
|
info = backend_info()
|
|
assert "requested" in info
|
|
assert "active" in info
|
|
assert "gopass_available" in info
|
|
assert "gopass_path" in info
|
|
assert "env_file" in info
|
|
|
|
|
|
def test_get_secret_from_file(tmp_path, monkeypatch):
|
|
"""Read a secret from a .env file when backend is file."""
|
|
env_file = tmp_path / "test.env"
|
|
env_file.write_text("PRY_FROM_FILE=hello\n# comment\nPRY_OTHER=world\n")
|
|
monkeypatch.setenv("PRY_SECRET_BACKEND", "file")
|
|
monkeypatch.setenv("PRY_ENV_FILE", str(env_file))
|
|
# Reimport to pick up the new env file
|
|
importlib.reload(secrets_backend)
|
|
try:
|
|
value = secrets_backend.get_secret("from_file")
|
|
assert value == "hello"
|
|
finally:
|
|
# Restore
|
|
monkeypatch.delenv("PRY_ENV_FILE", raising=False)
|
|
importlib.reload(secrets_backend)
|
|
|
|
|
|
def test_set_secret_returns_false_when_gopass_unavailable(monkeypatch):
|
|
monkeypatch.setattr(secrets_backend, "_GOPASS_AVAILABLE", False)
|
|
# Force the global to be re-evaluated
|
|
monkeypatch.setattr(secrets_backend, "_gopass_available", lambda: False)
|
|
result = set_secret("test_secret", "value")
|
|
assert result is False
|
|
|
|
|
|
def test_detect_backend_invalid_falls_back_to_auto(monkeypatch):
|
|
monkeypatch.setenv("PRY_SECRET_BACKEND", "not-a-real-backend")
|
|
backend = secrets_backend._detect_backend()
|
|
assert backend == BACKEND_AUTO
|