mirror of
https://github.com/Sea-Haven-Industries/pr-reviewer.git
synced 2026-09-30 05:43:16 +00:00
133 lines
4.1 KiB
Python
133 lines
4.1 KiB
Python
|
|
"""Shared test fixtures and stdlib-only httpx2 stubs.
|
||
|
|
|
||
|
|
The app creates ``httpx2.Client(...)`` inline inside each method, so we mock the
|
||
|
|
network by monkeypatching ``httpx2.Client`` in the target module namespace with a
|
||
|
|
``FakeClient`` that records requests and returns canned ``FakeResponse`` objects.
|
||
|
|
No real network, no secrets, fully offline.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from __future__ import annotations
|
||
|
|
|
||
|
|
import json
|
||
|
|
from types import SimpleNamespace
|
||
|
|
from typing import Any
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
|
||
|
|
|
||
|
|
class FakeResponse:
|
||
|
|
"""Minimal stand-in for ``httpx2.Response`` covering what the app touches:
|
||
|
|
``status_code``, ``.json()``, ``.text``, and ``.raise_for_status()``."""
|
||
|
|
|
||
|
|
def __init__(
|
||
|
|
self,
|
||
|
|
status_code: int = 200,
|
||
|
|
*,
|
||
|
|
json_data: Any = None,
|
||
|
|
text: str | None = None,
|
||
|
|
) -> None:
|
||
|
|
self.status_code = status_code
|
||
|
|
self._json = json_data
|
||
|
|
self._json_is_set = json_data is not None
|
||
|
|
if text is not None:
|
||
|
|
self.text = text
|
||
|
|
elif json_data is not None:
|
||
|
|
self.text = json.dumps(json_data)
|
||
|
|
else:
|
||
|
|
self.text = ""
|
||
|
|
|
||
|
|
def json(self) -> Any:
|
||
|
|
if not self._json_is_set:
|
||
|
|
# Mimic httpx/requests raising when the body is not valid JSON.
|
||
|
|
raise json.JSONDecodeError("no json", self.text or "", 0)
|
||
|
|
return self._json
|
||
|
|
|
||
|
|
def raise_for_status(self) -> None:
|
||
|
|
if self.status_code >= 400:
|
||
|
|
raise RuntimeError(f"HTTP {self.status_code}")
|
||
|
|
|
||
|
|
|
||
|
|
class FakeClient:
|
||
|
|
"""Context-manager stub for ``httpx2.Client``.
|
||
|
|
|
||
|
|
Returns a single canned response for every ``get``/``post`` and appends each
|
||
|
|
call (method, url, kwargs) to ``calls`` so tests can assert on the payload.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, response: FakeResponse, calls: list[dict[str, Any]]) -> None:
|
||
|
|
self._response = response
|
||
|
|
self._calls = calls
|
||
|
|
|
||
|
|
def __enter__(self) -> FakeClient:
|
||
|
|
return self
|
||
|
|
|
||
|
|
def __exit__(self, *exc: object) -> bool:
|
||
|
|
return False
|
||
|
|
|
||
|
|
def get(self, url: str, **kwargs: Any) -> FakeResponse:
|
||
|
|
self._calls.append({"method": "GET", "url": url, **kwargs})
|
||
|
|
return self._response
|
||
|
|
|
||
|
|
def post(self, url: str, **kwargs: Any) -> FakeResponse:
|
||
|
|
self._calls.append({"method": "POST", "url": url, **kwargs})
|
||
|
|
return self._response
|
||
|
|
|
||
|
|
|
||
|
|
def patch_httpx(monkeypatch: pytest.MonkeyPatch, module: Any, response: FakeResponse):
|
||
|
|
"""Patch ``module.httpx2.Client`` to hand back a ``FakeClient``.
|
||
|
|
|
||
|
|
Returns the ``calls`` list that records every request made through it.
|
||
|
|
"""
|
||
|
|
calls: list[dict[str, Any]] = []
|
||
|
|
|
||
|
|
def _factory(*_args: Any, **_kwargs: Any) -> FakeClient:
|
||
|
|
return FakeClient(response, calls)
|
||
|
|
|
||
|
|
monkeypatch.setattr(module.httpx2, "Client", _factory)
|
||
|
|
return calls
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def make_cfg():
|
||
|
|
"""Factory for a lightweight config object with sane test defaults.
|
||
|
|
|
||
|
|
Uses ``SimpleNamespace`` so tests never depend on env/.env and never need a
|
||
|
|
real ``Config``. Override any field via keyword args.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def _make(**overrides: Any) -> SimpleNamespace:
|
||
|
|
defaults: dict[str, Any] = {
|
||
|
|
# GitHub
|
||
|
|
"GITHUB_TOKEN": "test-token",
|
||
|
|
"GITHUB_ORG": "Sea-Haven-Industries",
|
||
|
|
"PR_SEARCH_FILTER": "is:pr state:open org:Sea-Haven-Industries",
|
||
|
|
"MAX_PRS": 30,
|
||
|
|
"MAX_DIFF_BYTES": 120000,
|
||
|
|
# Fireworks
|
||
|
|
"FIREWORKS_API_KEY": "fw_test_key",
|
||
|
|
"FIREWORKS_BASE_URL": "https://api.fireworks.ai/inference/v1",
|
||
|
|
"FIREWORKS_MODEL": "accounts/fireworks/models/deepseek-v4-pro",
|
||
|
|
"FIREWORKS_TEMPERATURE": 0.2,
|
||
|
|
"FIREWORKS_MAX_TOKENS": 4000,
|
||
|
|
# Review behavior
|
||
|
|
"MENTION_AUTHORS": ["openswe"],
|
||
|
|
"HOST": "127.0.0.1",
|
||
|
|
"PORT": 8765,
|
||
|
|
}
|
||
|
|
defaults.update(overrides)
|
||
|
|
return SimpleNamespace(**defaults)
|
||
|
|
|
||
|
|
return _make
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def sample_pr() -> dict[str, Any]:
|
||
|
|
return {
|
||
|
|
"owner": "Sea-Haven-Industries",
|
||
|
|
"repo": "pr-reviewer",
|
||
|
|
"number": 7,
|
||
|
|
"title": "Add caching layer",
|
||
|
|
"author": "octocat",
|
||
|
|
"body": "This PR adds a cache.",
|
||
|
|
}
|