"""Phase 2A (embedding-phase2a-1) — Qwen 후보 디스패처/쿼리 임베딩 단위 테스트.""" from __future__ import annotations import pytest from services.search import retrieval_service as rs def test_resolve_qwen_backends(): for slug in ("cand_qwen06", "cand_qwen4", "cand_qwen4m"): cfg = rs._resolve_backend(slug) assert cfg["docs_table"].startswith("documents_cand_qwen") assert cfg["chunks_table"].startswith("document_chunks_cand_qwen") assert cfg["embed_kind"] == "ollama" # 테이블명이 2단계 SQL allowlist 도 통과해야 함 (R2-B1) assert rs._VALID_DOCS_TABLE.match(cfg["docs_table"]) assert rs._VALID_CHUNKS_TABLE.match(cfg["chunks_table"]) assert rs._resolve_backend("baseline") is None with pytest.raises(ValueError): rs._resolve_backend("cand_unknown") def test_qwen4m_has_mrl_dimensions(): assert rs._resolve_backend("cand_qwen4m")["embed_dimensions"] == 1024 assert "embed_dimensions" not in rs._resolve_backend("cand_qwen4") class _FakeResp: def __init__(self, embs): self._embs = embs def raise_for_status(self): return None def json(self): return {"embeddings": self._embs} class _FakeClient: """httpx.AsyncClient 대역 — post body 캡처.""" captured: dict = {} def __init__(self, *a, **k): pass async def __aenter__(self): return self async def __aexit__(self, *a): return False async def post(self, url, json=None): _FakeClient.captured = {"url": url, "json": json} dim = (json or {}).get("dimensions") or 1024 return _FakeResp([[0.1] * dim]) @pytest.mark.asyncio async def test_ollama_query_embed_applies_instruct_prefix(monkeypatch): import httpx monkeypatch.setattr(httpx, "AsyncClient", _FakeClient) cfg = rs._resolve_backend("cand_qwen06") out = await rs._embed_query_via_ollama(cfg, "압력용기 수압시험") assert out is not None and len(out) == 1024 body = _FakeClient.captured["json"] assert body["model"] == "qwen3-embedding:0.6b" assert body["input"][0].startswith(rs.QWEN3_QUERY_INSTRUCT) assert body["input"][0].endswith("압력용기 수압시험") assert "dimensions" not in body @pytest.mark.asyncio async def test_ollama_query_embed_mrl_dimensions(monkeypatch): import httpx monkeypatch.setattr(httpx, "AsyncClient", _FakeClient) cfg = rs._resolve_backend("cand_qwen4m") out = await rs._embed_query_via_ollama(cfg, "q") assert _FakeClient.captured["json"]["dimensions"] == 1024 assert len(out) == 1024 @pytest.mark.asyncio async def test_ollama_query_embed_failure_returns_none(monkeypatch): import httpx class _Boom(_FakeClient): async def post(self, url, json=None): raise httpx.ConnectError("down") monkeypatch.setattr(httpx, "AsyncClient", _Boom) cfg = rs._resolve_backend("cand_qwen06") assert await rs._embed_query_via_ollama(cfg, "q") is None