From cd3047511cf2c9e882936712e6959af9e6122ff8 Mon Sep 17 00:00:00 2001 From: jp170na Date: Sun, 16 Aug 2026 16:58:16 +0200 Subject: [PATCH] pridanie testu --- test/test_rag_runner.py | 610 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 610 insertions(+) create mode 100644 test/test_rag_runner.py diff --git a/test/test_rag_runner.py b/test/test_rag_runner.py new file mode 100644 index 0000000..14e1a6d --- /dev/null +++ b/test/test_rag_runner.py @@ -0,0 +1,610 @@ +from __future__ import annotations + +import io +import urllib.error +from pathlib import Path +from typing import Any + +import pytest + +import evaluation.rag_runner as rag_runner + + +class FakeResponse: + def __init__( + self, + body: str, + ) -> None: + self.body = body + + def __enter__( + self, + ) -> FakeResponse: + return self + + def __exit__( + self, + exc_type: Any, + exc_value: Any, + traceback: Any, + ) -> bool: + return False + + def read( + self, + ) -> bytes: + return self.body.encode( + "utf-8" + ) + + +def make_http_error( + code: int, + *, + body: str = "temporary error", +) -> urllib.error.HTTPError: + return urllib.error.HTTPError( + url="https://example.test/api", + code=code, + msg="test error", + hdrs=None, + fp=io.BytesIO( + body.encode( + "utf-8" + ) + ), + ) + + +def test_retry_delay_seconds_uses_exponential_backoff() -> None: + assert ( + rag_runner.retry_delay_seconds( + 1, + backoff_base=1.0, + backoff_max=8.0, + ) + == 1.0 + ) + + assert ( + rag_runner.retry_delay_seconds( + 2, + backoff_base=1.0, + backoff_max=8.0, + ) + == 2.0 + ) + + assert ( + rag_runner.retry_delay_seconds( + 3, + backoff_base=1.0, + backoff_max=8.0, + ) + == 4.0 + ) + + assert ( + rag_runner.retry_delay_seconds( + 4, + backoff_base=1.0, + backoff_max=8.0, + ) + == 8.0 + ) + + assert ( + rag_runner.retry_delay_seconds( + 5, + backoff_base=1.0, + backoff_max=8.0, + ) + == 8.0 + ) + + +def test_request_json_retries_http_429_then_succeeds( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = 0 + + sleeps: list[ + float + ] = [] + + responses: list[ + Any + ] = [ + make_http_error( + 429 + ), + FakeResponse( + '{"ok": true}' + ), + ] + + def fake_urlopen( + request: Any, + timeout: int, + ) -> FakeResponse: + nonlocal calls + + calls += 1 + + response = responses[ + calls - 1 + ] + + if isinstance( + response, + Exception, + ): + raise response + + return response + + monkeypatch.setattr( + rag_runner.urllib.request, + "urlopen", + fake_urlopen, + ) + + monkeypatch.setattr( + rag_runner.time, + "sleep", + lambda delay: sleeps.append( + delay + ), + ) + + result = rag_runner.request_json( + "https://example.test/api", + max_attempts=3, + backoff_base=1.0, + backoff_max=8.0, + ) + + assert result == { + "ok": True, + } + + assert calls == 2 + + assert sleeps == [ + 1.0, + ] + + +def test_request_json_retries_http_503_then_succeeds( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = 0 + + sleeps: list[ + float + ] = [] + + responses: list[ + Any + ] = [ + make_http_error( + 503 + ), + FakeResponse( + '{"status": "ready"}' + ), + ] + + def fake_urlopen( + request: Any, + timeout: int, + ) -> FakeResponse: + nonlocal calls + + calls += 1 + + response = responses[ + calls - 1 + ] + + if isinstance( + response, + Exception, + ): + raise response + + return response + + monkeypatch.setattr( + rag_runner.urllib.request, + "urlopen", + fake_urlopen, + ) + + monkeypatch.setattr( + rag_runner.time, + "sleep", + lambda delay: sleeps.append( + delay + ), + ) + + result = rag_runner.request_json( + "https://example.test/api", + max_attempts=3, + backoff_base=1.0, + backoff_max=8.0, + ) + + assert result == { + "status": "ready", + } + + assert calls == 2 + + assert sleeps == [ + 1.0, + ] + + +def test_request_json_does_not_retry_http_400( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = 0 + + sleeps: list[ + float + ] = [] + + def fake_urlopen( + request: Any, + timeout: int, + ) -> FakeResponse: + nonlocal calls + + calls += 1 + + raise make_http_error( + 400, + body="bad request", + ) + + monkeypatch.setattr( + rag_runner.urllib.request, + "urlopen", + fake_urlopen, + ) + + monkeypatch.setattr( + rag_runner.time, + "sleep", + lambda delay: sleeps.append( + delay + ), + ) + + with pytest.raises( + rag_runner.RequestError + ) as exc_info: + rag_runner.request_json( + "https://example.test/api", + max_attempts=4, + backoff_base=1.0, + backoff_max=8.0, + ) + + error = exc_info.value + + assert error.retryable is False + + assert ( + error.status_code + == 400 + ) + + assert calls == 1 + + assert sleeps == [] + + +def test_request_json_retries_network_error_then_succeeds( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = 0 + + sleeps: list[ + float + ] = [] + + def fake_urlopen( + request: Any, + timeout: int, + ) -> FakeResponse: + nonlocal calls + + calls += 1 + + if calls == 1: + raise urllib.error.URLError( + "temporary network problem" + ) + + return FakeResponse( + '{"ok": true}' + ) + + monkeypatch.setattr( + rag_runner.urllib.request, + "urlopen", + fake_urlopen, + ) + + monkeypatch.setattr( + rag_runner.time, + "sleep", + lambda delay: sleeps.append( + delay + ), + ) + + result = rag_runner.request_json( + "https://example.test/api", + max_attempts=3, + backoff_base=0.5, + backoff_max=8.0, + ) + + assert result == { + "ok": True, + } + + assert calls == 2 + + assert sleeps == [ + 0.5, + ] + + +def test_request_json_does_not_retry_invalid_json( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = 0 + + sleeps: list[ + float + ] = [] + + def fake_urlopen( + request: Any, + timeout: int, + ) -> FakeResponse: + nonlocal calls + + calls += 1 + + return FakeResponse( + "this is not json" + ) + + monkeypatch.setattr( + rag_runner.urllib.request, + "urlopen", + fake_urlopen, + ) + + monkeypatch.setattr( + rag_runner.time, + "sleep", + lambda delay: sleeps.append( + delay + ), + ) + + with pytest.raises( + rag_runner.RequestError + ) as exc_info: + rag_runner.request_json( + "https://example.test/api", + max_attempts=4, + ) + + error = exc_info.value + + assert error.retryable is False + + assert ( + error.status_code + is None + ) + + assert calls == 1 + + assert sleeps == [] + + +def test_request_json_stops_after_max_attempts( + monkeypatch: pytest.MonkeyPatch, +) -> None: + calls = 0 + + sleeps: list[ + float + ] = [] + + def fake_urlopen( + request: Any, + timeout: int, + ) -> FakeResponse: + nonlocal calls + + calls += 1 + + raise make_http_error( + 503 + ) + + monkeypatch.setattr( + rag_runner.urllib.request, + "urlopen", + fake_urlopen, + ) + + monkeypatch.setattr( + rag_runner.time, + "sleep", + lambda delay: sleeps.append( + delay + ), + ) + + with pytest.raises( + rag_runner.RequestError + ) as exc_info: + rag_runner.request_json( + "https://example.test/api", + max_attempts=3, + backoff_base=0.5, + backoff_max=8.0, + ) + + error = exc_info.value + + assert error.retryable is True + + assert ( + error.status_code + == 503 + ) + + assert calls == 3 + + assert sleeps == [ + 0.5, + 1.0, + ] + + +def test_request_json_validates_retry_configuration() -> None: + with pytest.raises( + ValueError, + match="timeout", + ): + rag_runner.request_json( + "https://example.test/api", + timeout=0, + ) + + with pytest.raises( + ValueError, + match="max_attempts", + ): + rag_runner.request_json( + "https://example.test/api", + max_attempts=0, + ) + + with pytest.raises( + ValueError, + match="backoff_base", + ): + rag_runner.request_json( + "https://example.test/api", + backoff_base=-1.0, + ) + + with pytest.raises( + ValueError, + match="backoff_max", + ): + rag_runner.request_json( + "https://example.test/api", + backoff_max=-1.0, + ) + + +def test_load_env_value_can_require_environment_only( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + env_path = ( + tmp_path + / ".env" + ) + + env_path.write_text( + ( + "OPENWEBUI_API_KEY=" + "secret-from-file\n" + ), + encoding="utf-8", + ) + + monkeypatch.delenv( + "OPENWEBUI_API_KEY", + raising=False, + ) + + with pytest.raises( + RuntimeError, + match=( + "priamo v environment" + ), + ): + rag_runner.load_env_value( + "OPENWEBUI_API_KEY", + env_path=env_path, + allow_env_file=False, + ) + + monkeypatch.setenv( + "OPENWEBUI_API_KEY", + "secret-from-environment", + ) + + value = ( + rag_runner.load_env_value( + "OPENWEBUI_API_KEY", + env_path=env_path, + allow_env_file=False, + ) + ) + + assert ( + value + == "secret-from-environment" + ) + + +def test_load_env_value_preserves_env_file_fallback( + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, +) -> None: + env_path = ( + tmp_path + / ".env" + ) + + env_path.write_text( + ( + "SEARCH_API_KEY=" + "search-secret-from-file\n" + ), + encoding="utf-8", + ) + + monkeypatch.delenv( + "SEARCH_API_KEY", + raising=False, + ) + + value = ( + rag_runner.load_env_value( + "SEARCH_API_KEY", + env_path=env_path, + ) + ) + + assert ( + value + == "search-secret-from-file" + )