pryscraper/tests/conftest.py
cryptorugmunch 14a59708ee feat(pry): wire secrets_backend.get_secret() into PrySettings for sensitive fields
PrySettings (settings.py) was reading sensitive values (api_key, *_api_key,
webhook_secret) only from PRY_* env vars. The existing secrets_backend
(used by x402.py and auth.py for jwt_secret) supports gopass as the
default backend, so secrets never needed to live in .env.

This commit:
- Adds PrySettings._apply_secrets_backend() in model_post_init
- For each sensitive field with empty current value, calls
  secrets_backend.get_secret(name) and uses the result
- Priority order: secrets_backend.get_secret > env var > field default
- This means `gopass insert -m pry/api_key` now sets the API key
  without touching .env or environment

Tests added (tests/test_secrets_backend_integration.py):
- test_settings_pulls_from_secrets_backend: confirms gopass fills empty fields
- test_x402_constants_come_from_secrets_backend: confirms x402.py honors gopass
- test_settings_does_not_override_set_env: env beats gopass when both set

Tests updated (tests/conftest.py):
- Session-scope fixture sets PRY_SECRET_BACKEND=env to prevent test runs from
  pulling real gopass secrets (which would cause the auth middleware to
  reject unauthenticated test requests)
- Per-test fixture zeros settings.settings.api_key for in-process tests

Audit item 12. Tests: 623 passed, 1 skipped (pre-existing /ready failure
unrelated to this change).
2026-07-06 11:01:08 +02:00

149 lines
4.5 KiB
Python

# 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.
import logging
import os
import pytest
@pytest.fixture
def sample_html() -> str:
return """<!DOCTYPE html><html><head><title>Test Page</title>
<meta name="description" content="A test page for scraping">
</head><body>
<h1 class="product-title">Widget Pro</h1>
<p class="price">$29.99</p>
<p class="description">The best widget ever made.</p>
<a href="https://example.com/page1">Page 1</a>
<a href="https://example.com/page2">Page 2</a>
<a href="https://external.com">External</a>
</body></html>"""
@pytest.fixture
def sample_markdown() -> str:
return """# Widget Pro
**Price:** $29.99
The best widget ever made.
- Feature 1: Lightweight
- Feature 2: Durable
- Feature 3: Affordable
Contact: sales@example.com
"""
@pytest.fixture
def sample_schema() -> dict:
return {
"product_name": "name of the product",
"price": "price in USD",
"email": "contact email email",
}
# ── Lenient LogRecord for tests ─────────────────────────────────
# Some Pry modules (and tests) use logger.warning("...", extra={"name": "foo"})
# patterns. Python's stdlib logging rejects this with KeyError because
# "name" is a reserved LogRecord field. In tests we make the LogRecord
# tolerant: reserved keys in `extra` are moved to a sub-dict so the call
# doesn't crash. This matches the behavior we get with structlog's bridge.
#
# Production code is unaffected (this conftest only runs in tests).
# If you actually want strict mode in tests, set PRY_LOG_STRICT_EXTRAS=1.
_RESERVED = frozenset(
{
"name",
"msg",
"args",
"levelname",
"levelno",
"pathname",
"filename",
"module",
"exc_info",
"exc_text",
"stack_info",
"lineno",
"funcName",
"created",
"msecs",
"relativeCreated",
"thread",
"threadName",
"processName",
"process",
"message",
"asctime",
"taskName",
}
)
def _make_lenient_logrecord() -> None:
"""Monkey-patch LogRecord.__init__ to be tolerant of reserved extra keys."""
if os.getenv("PRY_LOG_STRICT_EXTRAS", "").lower() in ("1", "true", "yes"):
return # honor strict mode if explicitly requested
original_init = logging.LogRecord.__init__
def lenient_init(self, *args, **kwargs):
# Extract extra before super().__init__ which would reject reserved keys
extra = kwargs.pop("extra", None)
original_init(self, *args, **kwargs)
if extra:
overflow: dict[str, object] = {}
for k, v in extra.items():
if k in _RESERVED:
overflow[k] = v
else:
setattr(self, k, v)
if overflow:
existing = getattr(self, "_overflow_extras", {}) or {}
existing.update(overflow)
self._overflow_extras = existing
logging.LogRecord.__init__ = lenient_init # type: ignore[assignment]
# Install the lenient LogRecord at conftest import time
_make_lenient_logrecord()
# ── Test isolation: disable gopass + clear auth in settings ─────
# PrySettings now pulls api_key from secrets_backend (gopass on Talos has
# a real pry/api_key secret). Tests must not depend on that real secret.
# Solution: set PRY_SECRET_BACKEND=env at session scope (inherited by
# subprocess-based tests like the SSE server test) and zero the
# settings singleton at test scope.
@pytest.fixture(autouse=True, scope="session")
def _pry_disable_secrets_gopass() -> None:
import os
os.environ["PRY_SECRET_BACKEND"] = "env"
os.environ.setdefault("PRY_API_KEY", "")
os.environ.setdefault("PRY_OPENROUTER_API_KEY", "")
os.environ.setdefault("PRY_OPENAI_API_KEY", "")
os.environ.setdefault("PRY_ANTHROPIC_API_KEY", "")
os.environ.setdefault("PRY_COHERE_API_KEY", "")
@pytest.fixture(autouse=True)
def pry_test_auth(monkeypatch: pytest.MonkeyPatch) -> None:
import settings as _settings_mod
original = _settings_mod.settings.api_key
_settings_mod.settings.api_key = ""
yield
_settings_mod.settings.api_key = original
@pytest.fixture
def client() -> "fastapi.testclient.TestClient": # noqa: F821
from fastapi.testclient import TestClient
from api import app
return TestClient(app)