- Split scraping endpoints from api.py into routers/scraping.py - Split template endpoints into routers/templates.py with POST /v1/templates/batch - Introduce deps.py for shared singleton instances - Add e2e tests for scraping, templates, auth, and Apify schema (23 new tests) - Add reusable Apify actor input/output schema builder (apify_schema.py) - Add PRY_API_KEY startup warning and deploy key to Talos /srv/pry/.env - Register /v1/templates/batch as paid x402 operation
70 lines
2.3 KiB
Python
70 lines
2.3 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.
|
|
"""Tests for API helpers and auth."""
|
|
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
from fastapi.testclient import TestClient
|
|
|
|
from api import app
|
|
|
|
|
|
@pytest.fixture
|
|
def client() -> TestClient:
|
|
return TestClient(app)
|
|
|
|
|
|
def test_get_or_key_from_env(monkeypatch) -> None:
|
|
monkeypatch.setattr("api.settings.openrouter_api_key", "sk-test-key-123")
|
|
from api import _get_or_key
|
|
|
|
key = _get_or_key()
|
|
assert key == "sk-test-key-123"
|
|
|
|
|
|
def test_get_or_key_empty_when_not_set(monkeypatch) -> None:
|
|
monkeypatch.setattr("api.settings.openrouter_api_key", "")
|
|
monkeypatch.setattr("os.path.isfile", lambda p: False)
|
|
from api import _get_or_key
|
|
|
|
key = _get_or_key()
|
|
assert key is None
|
|
|
|
|
|
def test_vision_models_fallback_list() -> None:
|
|
from api import VISION_MODELS_FALLBACK
|
|
|
|
assert len(VISION_MODELS_FALLBACK) >= 3
|
|
assert all("free" in m for m in VISION_MODELS_FALLBACK)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_api_key_required_when_set(client: TestClient, monkeypatch) -> None:
|
|
"""Protected endpoints require a valid Bearer PRY_API_KEY."""
|
|
monkeypatch.setattr("api.settings.api_key", "secret-pry-key")
|
|
# Disable x402 payment gating so we can test auth in isolation.
|
|
with patch("x402_middleware.X402Middleware.__call__", lambda self, scope, receive, send: self.app(scope, receive, send)):
|
|
resp = client.post("/v1/scrape", json={"url": "https://example.com"})
|
|
assert resp.status_code == 401
|
|
|
|
with patch("x402_middleware.X402Middleware.__call__", lambda self, scope, receive, send: self.app(scope, receive, send)):
|
|
resp = client.post(
|
|
"/v1/scrape",
|
|
json={"url": "https://example.com"},
|
|
headers={"authorization": "Bearer wrong-key"},
|
|
)
|
|
assert resp.status_code == 401
|
|
|
|
with patch("x402_middleware.X402Middleware.__call__", lambda self, scope, receive, send: self.app(scope, receive, send)):
|
|
resp = client.post(
|
|
"/v1/scrape",
|
|
json={"url": "https://example.com"},
|
|
headers={"authorization": "Bearer secret-pry-key"},
|
|
)
|
|
# auth passes; endpoint returns 402 because we did not mock scraper, which is fine
|
|
assert resp.status_code in (200, 402)
|
|
|