EverOS/tests/integration/test_api/test_health_capabilities.py

328 lines
10 KiB
Python

"""Tests for GET /health endpoint capabilities and disabled_features response."""
from __future__ import annotations
from pytest import mark, param
from everos.component.capabilities import compute_disabled_features
class TestComputeDisabledFeatures:
"""Test the compute_disabled_features helper function."""
def test_all_capabilities_available(self) -> None:
"""When all capabilities are available, no features are disabled."""
caps = {
"llm": True,
"embed": True,
"rerank": True,
"multimodal_llm": True,
"parser": True,
}
disabled = compute_disabled_features(caps)
assert disabled == []
def test_embedding_unavailable_disables_vector_search_features(
self,
) -> None:
"""When embedding unavailable, vector/hybrid/reflection/skill disabled."""
caps = {
"llm": True,
"embed": False,
"rerank": True,
"multimodal_llm": True,
"parser": True,
}
disabled = compute_disabled_features(caps)
expected = {
"vector_search",
"hybrid_search",
"reflection",
"skill_extraction",
"knowledge",
}
assert set(disabled) == expected
def test_rerank_unavailable_disables_agentic_search(self) -> None:
"""When rerank is unavailable, agentic_search is disabled."""
caps = {
"llm": True,
"embed": True,
"rerank": False,
"multimodal_llm": True,
"parser": True,
}
disabled = compute_disabled_features(caps)
assert "agentic_search" in disabled
def test_both_embed_and_rerank_missing_disables_knowledge(self) -> None:
"""When both embed and rerank are missing, knowledge is disabled."""
caps = {
"llm": True,
"embed": False,
"rerank": False,
"multimodal_llm": True,
"parser": True,
}
disabled = compute_disabled_features(caps)
assert "knowledge" in disabled
def test_multimodal_and_parser_missing_disables_multimodal_upload(self) -> None:
"""When multimodal_llm is unavailable, multimodal_upload is disabled."""
caps = {
"llm": True,
"embed": True,
"rerank": True,
"multimodal_llm": False,
"parser": True,
}
disabled = compute_disabled_features(caps)
assert "multimodal_upload" in disabled
def test_parser_missing_disables_multimodal_upload(self) -> None:
"""When parser is unavailable, multimodal_upload is disabled."""
caps = {
"llm": True,
"embed": True,
"rerank": True,
"multimodal_llm": True,
"parser": False,
}
disabled = compute_disabled_features(caps)
assert "multimodal_upload" in disabled
def test_tier1_only_llm(self) -> None:
"""Tier 1 (LLM only): many features are disabled."""
caps = {
"llm": True,
"embed": False,
"rerank": False,
"multimodal_llm": False,
"parser": False,
}
disabled = compute_disabled_features(caps)
# Tier 1 disables: vector/hybrid/agentic/reflection/skill/knowledge/multimodal
expected = {
"vector_search",
"hybrid_search",
"agentic_search",
"reflection",
"skill_extraction",
"knowledge",
"multimodal_upload",
}
assert set(disabled) == expected
@mark.parametrize(
"caps,expected_disabled",
[
param(
{
"llm": True,
"embed": True,
"rerank": True,
"multimodal_llm": True,
"parser": True,
},
[],
id="all_available",
),
param(
{
"llm": True,
"embed": False,
"rerank": True,
"multimodal_llm": True,
"parser": True,
},
[
"vector_search",
"hybrid_search",
"reflection",
"skill_extraction",
"knowledge",
],
id="no_embed",
),
param(
{
"llm": True,
"embed": True,
"rerank": False,
"multimodal_llm": True,
"parser": True,
},
["agentic_search", "knowledge"],
id="no_rerank",
),
],
)
def test_combinations(
self,
caps: dict[str, bool],
expected_disabled: list[str],
) -> None:
"""Parametrized tests for various capability combinations."""
disabled = compute_disabled_features(caps)
assert set(disabled) == set(expected_disabled)
@mark.asyncio
async def test_health_endpoint_includes_capabilities() -> None:
"""GET /health returns capabilities dict with all 5 provider fields."""
from httpx import ASGITransport, AsyncClient
from everos.entrypoints.api.app import create_app
app = create_app()
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport) as client:
response = await client.get("http://testserver/health")
assert response.status_code == 200
data = response.json()
assert "status" in data
assert "version" in data
assert "capabilities" in data
assert "disabled_features" in data
caps = data["capabilities"]
assert isinstance(caps, dict)
assert "llm" in caps
assert "embed" in caps
assert "rerank" in caps
assert "multimodal_llm" in caps
assert "parser" in caps
# All capability values should be boolean
for key, value in caps.items():
msg = f"Capability {key} should be bool, got {type(value)}"
assert isinstance(value, bool), msg
# disabled_features should be a list
disabled = data["disabled_features"]
assert isinstance(disabled, list)
for feature in disabled:
assert isinstance(feature, str)
@mark.asyncio
@mark.parametrize(
("embed", "rerank", "multimodal_llm", "parser", "expected_disabled"),
[
param(
False,
False,
False,
False,
{
"vector_search",
"hybrid_search",
"reflection",
"skill_extraction",
"agentic_search",
"knowledge",
"multimodal_upload",
},
id="tier1_llm_only",
),
param(
True,
False,
False,
False,
{"agentic_search", "knowledge", "multimodal_upload"},
id="tier2_embed_only",
),
param(
True,
True,
False,
False,
{"multimodal_upload"},
id="tier3_embed_rerank_only",
),
param(
True,
True,
True,
True,
set(),
id="full_stack_all_enabled",
),
],
)
async def test_health_endpoint_disabled_features_matches_hardcoded_expectation(
monkeypatch,
embed: bool,
rerank: bool,
multimodal_llm: bool,
parser: bool,
expected_disabled: set[str],
) -> None:
"""GET /health disabled_features must match a hard-coded expected set.
Pre-M12 this test asserted ``set(response['disabled_features']) ==
set(compute_disabled_features(response['capabilities']))`` — a
tautology, because the endpoint uses the same
:func:`compute_disabled_features` to build the field. Any bug in
the helper would be present on both sides of the equation and go
undetected. Round-2 finding M12 replaces the tautology with a
per-tier hard-coded expected set, driven by parametrisation over
every soft-dependency capability toggle — so a regression in
:func:`compute_disabled_features` (e.g. dropping the
``knowledge`` disable, mis-spelling a feature name) breaks the
test rather than staying invisible.
"""
from httpx import ASGITransport, AsyncClient
from everos.component import embedding, multimodal
from everos.component import rerank as rerank_mod
from everos.component.parser import _core as parser_core
from everos.entrypoints.api.app import create_app
class _StubCapability:
def __init__(self, available: bool) -> None:
self.available = available
monkeypatch.setattr(
embedding, "get_embedding_capability", lambda: _StubCapability(embed)
)
monkeypatch.setattr(
rerank_mod, "get_rerank_capability", lambda: _StubCapability(rerank)
)
monkeypatch.setattr(
multimodal,
"get_multimodal_llm_capability",
lambda: _StubCapability(multimodal_llm),
)
monkeypatch.setattr(parser_core, "parser_available", lambda: parser)
# The health route imports the accessors at module load; patch there
# too so the values it actually reads honour the monkeypatch.
from everos.entrypoints.api.routes import health as health_route
monkeypatch.setattr(
health_route, "get_embedding_capability", lambda: _StubCapability(embed)
)
monkeypatch.setattr(
health_route, "get_rerank_capability", lambda: _StubCapability(rerank)
)
monkeypatch.setattr(
health_route,
"get_multimodal_llm_capability",
lambda: _StubCapability(multimodal_llm),
)
monkeypatch.setattr(health_route, "parser_available", lambda: parser)
app = create_app()
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport) as client:
response = await client.get("http://testserver/health")
assert response.status_code == 200
data = response.json()
assert set(data["disabled_features"]) == expected_disabled