o2o-site-AEO/solution/backend/tests/test_itinerary_trigger.py

116 lines
4.5 KiB
Python

"""일정 생성은 **언제** 걸리나.
★ 에디터 요청 안에서 생성하지 않는다. 두 기간 합쳐 50~100초다 —
지역 이야기가 잡으로 도는 이유와 같다(local_content_service._ensure_region_stories L444).
캔버스는 잡을 넣고, 이번 응답에는 안 실린다. **다음에 열 때** 보인다.
★ 가드가 "이야기가 다 찼나" 만 보면 안 된다. 이미 이야기가 찬 업장은 일정을 영영 못 받는다 —
story_service 가 has_stories 하나로 판단하다 daily 를 못 받던 것과 같은 함정이다.
"""
import uuid
from services import story_service
from services.llm import perplexity
from services.local_content_service import LocalContentService
class _Place:
"""`_ensure_region_stories` 와 `run_local_sync` 가 getattr 로만 읽는 최소 객체."""
def __init__(self):
self.place_id = uuid.uuid4()
self.region_code = "52군산시"
self.name = "스테이,머뭄"
self.road_address = "전북특별자치도 군산시 절골길 18"
self.address = self.road_address
class _SyncResult:
"""`sync_place_by_id` 가 돌려주는 모양(ResSyncPlace)의 최소 대역."""
class result:
success = True
msg = ""
festivals = attractions = restaurants = 0
async def test_local_sync_job_generates_itineraries(monkeypatch, db_engine):
"""검증: LOCAL_SYNC 잡이 일정 생성을 부른다.
기대결과: place_id 로 ensure_generated_by_id 를 부르고 결과를 잡 결과에 싣는다.
★ 이야기 블록은 키가 없으면(APP_ENV=test 는 .env 를 안 읽는다) 조기 반환한다 —
그래서 일정 블록이 **이야기보다 앞**에 있어야 이 테스트가 통과한다. 그게 의도다."""
called: list = []
async def fake_ensure(place_id):
called.append(place_id)
return {"place_id": str(place_id), "counts": {"1박 2일": 5, "2박 3일": 5}, "notes": []}
async def fake_sync(self, place_id):
return _SyncResult()
monkeypatch.setattr("services.itinerary_llm_service.ensure_generated_by_id", fake_ensure)
monkeypatch.setattr(LocalContentService, "sync_place_by_id", fake_sync)
pid = uuid.uuid4()
out = await story_service.run_local_sync({
"payload": {"place_id": str(pid), "region_code": "52군산시",
"region_label": "전북특별자치도 군산시"},
})
assert called == [pid]
assert out["itineraries"]["counts"] == {"1박 2일": 5, "2박 3일": 5}
async def test_canvas_enqueues_job_when_only_itineraries_are_missing(monkeypatch, db_engine):
"""검증: 이야기는 다 찼지만 일정이 없는 업장이 캔버스를 연다.
기대결과: 잡을 넣는다 — 가드가 일정 누락도 보기 때문이다.
★ 이 테스트가 없으면 "이야기가 찬 업장은 일정을 영영 못 받는" 함정이 조용히 살아난다."""
enqueued: list = []
async def fake_enqueue(place):
enqueued.append(place.place_id)
return "job-1"
async def no_missing_kinds(region_code):
return []
async def two_missing_durations(place_id):
return ["1박 2일", "2박 3일"]
monkeypatch.setattr(perplexity, "is_configured", lambda: True)
monkeypatch.setattr(story_service, "missing_kinds", no_missing_kinds)
monkeypatch.setattr(story_service, "enqueue_region_job", fake_enqueue)
monkeypatch.setattr("services.itinerary_llm_service.missing_durations", two_missing_durations)
place = _Place()
await LocalContentService()._ensure_region_stories(place)
assert enqueued == [place.place_id]
async def test_canvas_does_not_enqueue_when_everything_is_filled(monkeypatch, db_engine):
"""검증: 이야기도 일정도 다 있는 업장.
기대결과: 잡을 넣지 않는다 — 유료 호출을 다시 걸지 않는다(요금 가드)."""
enqueued: list = []
async def fake_enqueue(place):
enqueued.append(place.place_id)
return "job-1"
async def no_missing_kinds(region_code):
return []
async def no_missing_durations(place_id):
return []
monkeypatch.setattr(perplexity, "is_configured", lambda: True)
monkeypatch.setattr(story_service, "missing_kinds", no_missing_kinds)
monkeypatch.setattr(story_service, "enqueue_region_job", fake_enqueue)
monkeypatch.setattr("services.itinerary_llm_service.missing_durations", no_missing_durations)
await LocalContentService()._ensure_region_stories(_Place())
assert enqueued == []