"""Unit tests for embedding observability. Covers: 1. ``Settings.get_embedding_provider_family()`` — the single source of truth for the ``provider`` metric label / span attribute — across provider configs. 2. The ``record_embedding`` helper — that it increments the right ``astrolabe_embedding_*`` series and skips the throughput counters on error. """ from __future__ import annotations import pytest from nextcloud_mcp_server.config import Settings from nextcloud_mcp_server.observability.metrics import ( record_embedding, record_embedding_tokens, ) pytestmark = pytest.mark.unit # ``metric_sample`` is provided as a shared fixture in tests/unit/conftest.py. class TestProviderFamily: """Provider-family detection mirrors ProviderRegistry priority.""" def test_bedrock(self): assert ( Settings(aws_region="us-east-1").get_embedding_provider_family() == "bedrock" ) def test_openai(self): settings = Settings( openai_api_key="sk-test", aws_region=None, bedrock_embedding_model=None, bedrock_generation_model=None, ) assert settings.get_embedding_provider_family() == "openai" def test_mistral(self): settings = Settings( mistral_api_key="m-test", aws_region=None, bedrock_embedding_model=None, bedrock_generation_model=None, openai_api_key=None, ) assert settings.get_embedding_provider_family() == "mistral" def test_ollama(self): settings = Settings( ollama_base_url="http://localhost:11434", aws_region=None, bedrock_embedding_model=None, bedrock_generation_model=None, openai_api_key=None, mistral_api_key=None, ) assert settings.get_embedding_provider_family() == "ollama" def test_simple_fallback(self): settings = Settings( aws_region=None, bedrock_embedding_model=None, bedrock_generation_model=None, openai_api_key=None, mistral_api_key=None, ollama_base_url=None, ) assert settings.get_embedding_provider_family() == "simple" def test_gateway_uses_model_prefix(self): settings = Settings( embedding_provider="gateway", embedding_gateway_url="https://gateway:8080", embedding_gateway_model="mistral/mistral-embed", ) assert settings.get_embedding_provider_family() == "mistral" class TestRecordEmbedding: def test_dense_success_increments_throughput(self, metric_sample): labels = {"kind": "dense", "provider": "uttest-prov"} before_chunks = metric_sample("astrolabe_embedding_chunks_total", labels) before_chars = metric_sample("astrolabe_embedding_chars_total", labels) before_req = metric_sample( "astrolabe_embedding_requests_total", {**labels, "status": "success"} ) record_embedding("dense", "uttest-prov", 0.42, chunks=12, chars=3400) assert metric_sample( "astrolabe_embedding_chunks_total", labels ) == pytest.approx(before_chunks + 12) assert metric_sample( "astrolabe_embedding_chars_total", labels ) == pytest.approx(before_chars + 3400) assert metric_sample( "astrolabe_embedding_requests_total", {**labels, "status": "success"} ) == pytest.approx(before_req + 1) assert ( metric_sample( "astrolabe_embedding_duration_seconds_count", {**labels, "status": "success"}, ) >= 1 ) def test_sparse_error_skips_throughput(self, metric_sample): labels = {"kind": "sparse", "provider": "bm25-uttest"} record_embedding( "sparse", "bm25-uttest", 0.1, chunks=5, chars=100, status="error" ) assert metric_sample( "astrolabe_embedding_chunks_total", labels ) == pytest.approx(0.0) assert metric_sample( "astrolabe_embedding_chars_total", labels ) == pytest.approx(0.0) assert metric_sample( "astrolabe_embedding_requests_total", {**labels, "status": "error"} ) == pytest.approx(1.0) class TestRecordEmbeddingTokens: """astrolabe_embedding_tokens_total — token cost split by index/query.""" def test_index_increments_by_token_count(self, metric_sample): labels = {"provider": "tok-prov", "operation": "index"} before = metric_sample("astrolabe_embedding_tokens_total", labels) record_embedding_tokens("tok-prov", "index", 4242) assert metric_sample( "astrolabe_embedding_tokens_total", labels ) == pytest.approx(before + 4242) def test_query_operation_is_separate_series(self, metric_sample): labels = {"provider": "tok-prov", "operation": "query"} before = metric_sample("astrolabe_embedding_tokens_total", labels) record_embedding_tokens("tok-prov", "query", 7) assert metric_sample( "astrolabe_embedding_tokens_total", labels ) == pytest.approx(before + 7) def test_zero_or_negative_is_noop(self, metric_sample): labels = {"provider": "tok-noop", "operation": "index"} record_embedding_tokens("tok-noop", "index", 0) record_embedding_tokens("tok-noop", "index", -3) assert metric_sample( "astrolabe_embedding_tokens_total", labels ) == pytest.approx(0.0)