Initial commit: import from sinmb79/Gov-chat-bot
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,151 @@
|
||||
"""
|
||||
Tier A 라우팅 통합 테스트.
|
||||
"""
|
||||
import pytest
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from uuid import uuid4
|
||||
|
||||
from app.services.routing import ResponseRouter, RoutingResult
|
||||
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,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tier_a_returns_when_faq_found():
|
||||
"""벡터DB에서 FAQ 매칭 → Tier A 반환."""
|
||||
faq_id = str(uuid4())
|
||||
faq = MagicMock()
|
||||
faq.id = faq_id
|
||||
faq.answer = "여권은 민원과에서 발급합니다."
|
||||
faq.question = "여권 발급 방법"
|
||||
faq.is_active = True
|
||||
faq.hit_count = 0
|
||||
faq.updated_at = None
|
||||
|
||||
embedding = AsyncMock()
|
||||
embedding.embed = AsyncMock(return_value=[[0.1] * 768])
|
||||
|
||||
vectordb = AsyncMock()
|
||||
vectordb.search = AsyncMock(return_value=[
|
||||
SearchResult(text="여권 발급 방법\n여권은 민원과에서 발급합니다.",
|
||||
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)
|
||||
|
||||
assert result.tier == "A"
|
||||
assert result.source == "faq"
|
||||
assert result.answer == faq.answer
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_tier_a_skipped_when_no_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"
|
||||
assert result.source == "fallback"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_routing_result_has_faq_id():
|
||||
"""Tier A 결과에 faq_id가 포함된다."""
|
||||
faq_id = str(uuid4())
|
||||
faq = MagicMock()
|
||||
faq.id = faq_id
|
||||
faq.answer = "답변"
|
||||
faq.question = "질문"
|
||||
faq.is_active = True
|
||||
faq.hit_count = 0
|
||||
faq.updated_at = None
|
||||
|
||||
embedding = AsyncMock()
|
||||
embedding.embed = AsyncMock(return_value=[[0.1] * 768])
|
||||
|
||||
vectordb = AsyncMock()
|
||||
vectordb.search = AsyncMock(return_value=[
|
||||
SearchResult(text="Q\nA", doc_id=f"{faq_id}_0", score=0.90,
|
||||
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)
|
||||
|
||||
assert result.faq_id == faq_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_routing_result_tier_d_has_fallback_info():
|
||||
"""Tier D 결과에 담당부서·전화번호가 포함된다."""
|
||||
router = make_router()
|
||||
result = await router.route("tenant-1", "알 수 없는 질문", "user-1", db=None)
|
||||
|
||||
assert result.tier == "D"
|
||||
assert "031-860-2000" in result.answer
|
||||
assert "민원과" in result.answer
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_timeout_returns_tier_d_is_timeout():
|
||||
"""타임아웃 발생 시 Tier D + is_timeout=True."""
|
||||
import asyncio
|
||||
|
||||
async def slow_embed(texts):
|
||||
await asyncio.sleep(10) # 타임아웃보다 긴 대기
|
||||
return [[0.0] * 768]
|
||||
|
||||
embedding = AsyncMock()
|
||||
embedding.embed = AsyncMock(side_effect=slow_embed)
|
||||
|
||||
vectordb = AsyncMock()
|
||||
|
||||
# 타임아웃을 매우 짧게 설정
|
||||
router = make_router(embedding=embedding, vectordb=vectordb)
|
||||
router.TIMEOUT_MS = 50 # 50ms
|
||||
|
||||
db = AsyncMock()
|
||||
result = await router.route("tenant-1", "질문", "user-1", db=db)
|
||||
|
||||
assert result.tier == "D"
|
||||
assert result.is_timeout is True
|
||||
Reference in New Issue
Block a user