"""Tests für hightrusted_capture.Client.""" from __future__ import annotations import json import pytest import responses from hightrusted_capture import ( Client, InvalidApiKeyError, QuotaExceededError, RateLimitedError, verify_webhook_signature, ) from hightrusted_capture.client import DEFAULT_BASE_URL @pytest.fixture def client() -> Client: return Client(api_key="ht_test_abc123", max_retries=0) # ─────────────────────────────────────────────────────────────────── # Capture # ─────────────────────────────────────────────────────────────────── @responses.activate def test_capture_sync_returns_object(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/captures", json={ "id": "550e8400-e29b-41d4-a716-446655440000", "status": "ready", "url": "https://example.com", "verify_url": "https://verify.hightrusted.net/c/550e", "created_at": "2026-04-25T14:32:08Z", }, status=200, ) capture = client.capture("https://example.com") assert capture["status"] == "ready" assert capture["id"] == "550e8400-e29b-41d4-a716-446655440000" @responses.activate def test_capture_sends_authorization_header(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/captures", json={"id": "x", "status": "ready", "created_at": "2026-04-25T00:00:00Z"}, status=200, ) client.capture("https://example.com") assert responses.calls[0].request.headers["Authorization"] == "Bearer ht_test_abc123" @responses.activate def test_capture_with_reference_and_viewport(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/captures", json={"id": "x", "status": "ready", "created_at": "2026-04-25T00:00:00Z"}, status=200, ) client.capture( "https://example.com", reference="case-001", viewport={"width": 1920, "height": 1080}, ) body = json.loads(responses.calls[0].request.body) assert body["reference"] == "case-001" assert body["viewport"]["width"] == 1920 # ─────────────────────────────────────────────────────────────────── # Verify # ─────────────────────────────────────────────────────────────────── @responses.activate def test_verify_by_id(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/verify", json={"valid": True, "capture_id": "550e"}, status=200, ) result = client.verify(source="550e8400-e29b-41d4-a716-446655440000") assert result["valid"] is True def test_verify_requires_exactly_one_source(client: Client) -> None: with pytest.raises(ValueError, match="Genau einer"): client.verify() with pytest.raises(ValueError, match="Genau einer"): client.verify(source="x", pdf="y") # ─────────────────────────────────────────────────────────────────── # Errors # ─────────────────────────────────────────────────────────────────── @responses.activate def test_invalid_api_key_raises(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/captures", json={"error": {"code": "invalid_api_key", "message": "Bad key", "request_id": "r1"}}, status=401, ) with pytest.raises(InvalidApiKeyError) as exc_info: client.capture("https://example.com") assert exc_info.value.code == "invalid_api_key" assert exc_info.value.request_id == "r1" @responses.activate def test_quota_exceeded_raises(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/captures", json={"error": {"code": "quota_exceeded", "message": "X", "request_id": "r1"}}, status=402, ) with pytest.raises(QuotaExceededError): client.capture("https://example.com") @responses.activate def test_rate_limit_includes_retry_after(client: Client) -> None: responses.add( responses.POST, f"{DEFAULT_BASE_URL}/captures", json={"error": {"code": "rate_limited", "message": "X", "request_id": "r1"}}, status=429, headers={"Retry-After": "12"}, ) with pytest.raises(RateLimitedError) as exc_info: client.capture("https://example.com") assert exc_info.value.retry_after_seconds == 12 # ─────────────────────────────────────────────────────────────────── # Webhook-Signatur # ─────────────────────────────────────────────────────────────────── def test_verify_webhook_signature_matches() -> None: body = b'{"event":"capture.ready"}' secret = "wh_secret_test" # Erwartet: sha256=hmac(body, secret) import hashlib import hmac sig = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest() assert verify_webhook_signature(body, sig, secret) is True def test_verify_webhook_signature_rejects_wrong_secret() -> None: body = b'{"event":"capture.ready"}' bad_sig = "sha256=" + "0" * 64 assert verify_webhook_signature(body, bad_sig, "secret") is False def test_verify_webhook_signature_rejects_empty_inputs() -> None: assert verify_webhook_signature(b"", "sha256=...", "secret") is False assert verify_webhook_signature(b"x", "", "secret") is False assert verify_webhook_signature(b"x", "sha256=...", "") is False # ─────────────────────────────────────────────────────────────────── # Konfiguration # ─────────────────────────────────────────────────────────────────── def test_client_requires_api_key(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.delenv("HIGHTRUSTED_API_KEY", raising=False) with pytest.raises(ValueError, match="API-Key fehlt"): Client() def test_client_reads_api_key_from_env(monkeypatch: pytest.MonkeyPatch) -> None: monkeypatch.setenv("HIGHTRUSTED_API_KEY", "ht_test_from_env") client = Client() assert client.api_key == "ht_test_from_env"