"""Test fixtures: a fake upstream (httpx-shaped) so no sockets are involved. The app only ever calls `client.get(...)`, so a stub with an async `get` is a complete substitute for httpx.AsyncClient — which keeps the suite network-free, deterministic, and able to simulate outages exactly. """ from __future__ import annotations import httpx import pytest from fastapi.testclient import TestClient import app as app_module GUFO_FIXTURE = """# HELP llamacpp:requests_processing how many requests are processing # TYPE llamacpp:requests_processing gauge llama:requests_processing 99 llamacpp:requests_processing 2 # TYPE llamacpp:requests_deferred gauge llamacpp:requests_deferred 1 # TYPE llamacpp:kv_cache_usage_ratio gauge llamacpp:kv_cache_usage_ratio 0.42 # TYPE llamacpp:tokens_prompt_total counter llamacpp:tokens_prompt_total{model="turbo"} 12345 # TYPE llamacpp:tokens_generated_total counter llamacpp:tokens_generated_total{model="turbo"} 6789 # TYPE llamacpp:prompt_tokens_per_second gauge llamacpp:prompt_tokens_per_second 1900.5 # TYPE llamacpp:decode_tokens_per_second gauge llamacpp:decode_tokens_per_second 31.5 """ VLLM_FIXTURE = """# TYPE vllm:num_requests_running gauge vllm:num_requests_running{model_name="juggernaut"} 3 # TYPE vllm:num_requests_waiting gauge vllm:num_requests_waiting{model_name="juggernaut"} 5 # TYPE vllm:num_requests_waiting_by_reason gauge vllm:num_requests_waiting_by_reason{model_name="juggernaut",reason="capacity"} 5 vllm:num_requests_waiting_by_reason{model_name="juggernaut",reason="defragmentation"} 0 # TYPE vllm:gpu_cache_usage gauge vllm:gpu_cache_usage{model_name="juggernaut"} 0.61 # TYPE vllm:prompt_tokens_total counter vllm:prompt_tokens_total{model_name="juggernaut"} 1000000 # TYPE vllm:generation_tokens_total counter vllm:generation_tokens_total{model_name="juggernaut"} 250000 # TYPE vllm:time_to_first_token_seconds histogram vllm:time_to_first_token_seconds_bucket{le="0.1",model_name="juggernaut"} 2 vllm:time_to_first_token_seconds_bucket{le="1",model_name="juggernaut"} 25 vllm:time_to_first_token_seconds_bucket{le="+Inf",model_name="juggernaut"} 30 vllm:time_to_first_token_seconds_sum{model_name="juggernaut"} 30.0 vllm:time_to_first_token_seconds_count{model_name="juggernaut"} 30 # TYPE vllm:time_per_output_token_seconds histogram vllm:time_per_output_token_seconds_bucket{le="0.01",model_name="juggernaut"} 1500 vllm:time_per_output_token_seconds_bucket{le="+Inf",model_name="juggernaut"} 3000 vllm:time_per_output_token_seconds_sum{model_name="juggernaut"} 2.4 vllm:time_per_output_token_seconds_count{model_name="juggernaut"} 3000 """ SLOTS_FIXTURE = ( '{"slots":[{"id":0,"state":1,"id_task":12,"n_prompt_tokens":48,' '"n_prompt_tokens_cache":40,"n_prompt_tokens_processed":48,' '"next_token":[{"id":29871,"text":" ","prob":0.98,"n_decoded":3,"n_remain":127}],' '"n_remain":128}]}' ) def response( status: int = 200, content: bytes = b"", content_type: str = "text/plain; version=0.0.4", ) -> httpx.Response: # A real httpx.Response rather than a mock: .text / .content / .reason_phrase # then behave exactly as app.fetch() expects. Attaching a request is mandatory # before a body is read. return httpx.Response( status, content=content, headers={"content-type": content_type}, request=httpx.Request("GET", "http://upstream.test"), ) class FakeUpstream: """Stands in for httpx.AsyncClient and records what was requested.""" def __init__(self) -> None: self.routes: dict[str, object] = {} self.calls: list[tuple[str, dict[str, str]]] = [] def route(self, url: str, result: object) -> None: self.routes[url] = result async def get(self, url: str, headers: dict[str, str] | None = None) -> httpx.Response: self.calls.append((url, dict(headers or {}))) result = self.routes.get(url) if isinstance(result, Exception): raise result if result is None: return response(404, b'{"detail":"Not Found"}', "application/json") return result # type: ignore[return-value] @property def requested_urls(self) -> list[str]: return [url for url, _headers in self.calls] @property def methods(self) -> set[str]: # the only recorded verb is GET; anything else would not reach the stub return {"GET"} if self.calls else set() @pytest.fixture def upstream(monkeypatch) -> FakeUpstream: fake = FakeUpstream() monkeypatch.setattr(app_module, "GUFO_BASE_URL", "http://gufo.test") monkeypatch.setattr(app_module, "VLLM_BASE_URL", "http://vllm.test") monkeypatch.setattr(app_module, "GUFO_API_KEY", "") monkeypatch.setattr(app_module, "VLLM_API_KEY", "") return fake @pytest.fixture def client(upstream: FakeUpstream): """TestClient without `with` => lifespan skipped, so we inject the stub client.""" test_client = TestClient(app_module.app) app_module.app.state.client = upstream return test_client