Files
Gov-chat-bot/backend/tests/test_tier_b_routing.py
2026-03-26 12:49:43 +09:00

145 lines
4.1 KiB
Python

"""
Tier B RAG 라우팅 테스트.
"""
import pytest
from unittest.mock import AsyncMock, MagicMock
from uuid import uuid4
from datetime import datetime
from app.services.routing import ResponseRouter
from app.providers.base import SearchResult
def make_router(embedding=None, vectordb=None) -> ResponseRouter:
providers = {}
if embedding:
providers["embedding"] = embedding
if vectordb:
providers["vectordb"] = vectordb
return ResponseRouter(
tenant_config={
"phone_number": "031-860-2000",
"fallback_dept": "민원과",
"tenant_name": "동두천시",
},
providers=providers,
)
def make_doc(doc_id: str) -> MagicMock:
doc = MagicMock()
doc.id = doc_id
doc.filename = "안내문.txt"
doc.is_active = True
doc.published_at = datetime(2026, 3, 1)
return doc
@pytest.mark.asyncio
async def test_tier_b_returns_when_doc_found():
"""문서 RAG 매칭 → Tier B 반환."""
doc_id = str(uuid4())
doc = make_doc(doc_id)
embedding = AsyncMock()
embedding.embed = AsyncMock(return_value=[[0.1] * 768])
vectordb = AsyncMock()
vectordb.search = AsyncMock(return_value=[
SearchResult(text="여권은 민원과에서 발급합니다.", doc_id=f"{doc_id}_0",
score=0.75, metadata={"doc_id": doc_id})
])
db = AsyncMock()
db.execute = AsyncMock()
scalar = MagicMock()
scalar.scalar_one_or_none = MagicMock(return_value=doc)
db.execute.return_value = scalar
router = make_router(embedding=embedding, vectordb=vectordb)
result = await router.route("tenant-1", "여권 발급 방법", "user-1", db=db)
assert result.tier == "B"
assert result.source == "rag"
@pytest.mark.asyncio
async def test_tier_b_skipped_when_no_doc_match():
"""문서 매칭 없으면 Tier D."""
embedding = AsyncMock()
embedding.embed = AsyncMock(return_value=[[0.1] * 768])
vectordb = AsyncMock()
vectordb.search = AsyncMock(return_value=[])
db = AsyncMock()
router = make_router(embedding=embedding, vectordb=vectordb)
result = await router.route("tenant-1", "모르는 질문", "user-1", db=db)
assert result.tier == "D"
@pytest.mark.asyncio
async def test_tier_b_result_has_doc_name():
"""Tier B 결과에 doc_name 포함."""
doc_id = str(uuid4())
doc = make_doc(doc_id)
embedding = AsyncMock()
embedding.embed = AsyncMock(return_value=[[0.1] * 768])
vectordb = AsyncMock()
vectordb.search = AsyncMock(return_value=[
SearchResult(text="내용", doc_id=f"{doc_id}_0", score=0.72,
metadata={"doc_id": doc_id})
])
db = AsyncMock()
db.execute = AsyncMock()
scalar = MagicMock()
scalar.scalar_one_or_none = MagicMock(return_value=doc)
db.execute.return_value = scalar
router = make_router(embedding=embedding, vectordb=vectordb)
result = await router.route("tenant-1", "질문", "user-1", db=db)
assert result.doc_name is not None
@pytest.mark.asyncio
async def test_tier_a_takes_priority_over_tier_b():
"""FAQ 유사도 ≥ 0.85 → Tier A (Tier B 미실행)."""
faq_id = str(uuid4())
faq = MagicMock()
faq.id = faq_id
faq.answer = "FAQ 답변"
faq.question = "질문"
faq.is_active = True
faq.hit_count = 0
faq.updated_at = None
embedding = AsyncMock()
embedding.embed = AsyncMock(return_value=[[0.1] * 768])
# FAQ 검색: 높은 유사도
vectordb = AsyncMock()
vectordb.search = AsyncMock(return_value=[
SearchResult(text="Q\nA", doc_id=f"{faq_id}_0", score=0.92,
metadata={"faq_id": faq_id})
])
db = AsyncMock()
db.execute = AsyncMock()
scalar = MagicMock()
scalar.scalar_one_or_none = MagicMock(return_value=faq)
db.execute.return_value = scalar
db.get = AsyncMock(return_value=faq)
db.commit = AsyncMock()
router = make_router(embedding=embedding, vectordb=vectordb)
result = await router.route("tenant-1", "질문", "user-1", db=db)
# FAQ가 먼저 매칭되면 Tier A
assert result.tier == "A"