- 지역 이야기(가요·인물·연표·엽서·퀴즈) 생성 경로: story_service · grounding/story · section_prompts. 지금까지 만들 자리가 없어 시안에만 손으로 넣은 3만 자였다 - 발행본 섹션: ItinerarySection · Carousel 레일 자동재생(use-rail-autoplay) · Festival · LocalGuide · Weather · Gallery · Header/Footer - 목업 payload 를 payloads-mockup/ 으로 분리 — 발행 대상과 섞이지 않게 - DB 새 구조 후속: site_payload · local_content_crud 조인 정리 · 테스트 - 마이그레이션 주석 축약: 9개 파일 합계 주석 비율 48% → 25%. 실측과 밟은 함정만 남기고 논증은 커밋 메시지로 옮겼다 검증: site·frontend 빌드 통과 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
62 lines
2.0 KiB
Python
62 lines
2.0 KiB
Python
from datetime import datetime, timedelta, timezone
|
|
|
|
from common.enums import ErrorType
|
|
from services.external.open_meteo import OpenMeteoRequestFailed
|
|
|
|
|
|
WEATHER = {
|
|
"temperature": 24.3,
|
|
"weather_code": 1,
|
|
"wind_speed": 3.2,
|
|
"observed_at": "2026-08-27T14:00",
|
|
"timezone": "Asia/Seoul",
|
|
"latitude": 33.46,
|
|
"longitude": 126.31,
|
|
}
|
|
|
|
|
|
async def test_weather_uses_region_cache(client, monkeypatch):
|
|
calls = 0
|
|
|
|
async def fetch(_lat, _lon):
|
|
nonlocal calls
|
|
calls += 1
|
|
return WEATHER
|
|
|
|
monkeypatch.setattr("services.local_content_service.fetch_current_weather", fetch)
|
|
params = {"region_code": "50110253", "latitude": 33.46, "longitude": 126.31}
|
|
|
|
first = (await client.get("/v1/local/weather", params=params)).json()
|
|
second = (await client.get("/v1/local/weather", params=params)).json()
|
|
|
|
assert first["weather"]["temperature"] == 24.3
|
|
assert first["cached"] is False
|
|
assert second["cached"] is True
|
|
assert calls == 1
|
|
|
|
|
|
async def test_weather_returns_stale_cache_when_upstream_fails(client, db_engine, monkeypatch):
|
|
params = {"region_code": "50110253", "latitude": 33.46, "longitude": 126.31}
|
|
monkeypatch.setattr("services.local_content_service.fetch_current_weather", lambda *_: _async_value(WEATHER))
|
|
await client.get("/v1/local/weather", params=params)
|
|
|
|
async with db_engine.begin() as conn:
|
|
from sqlalchemy import text
|
|
await conn.execute(text("UPDATE area_contents SET expires_at = :expired"), {
|
|
"expired": datetime.now(timezone.utc) - timedelta(minutes=1)
|
|
})
|
|
|
|
async def fail(*_args):
|
|
raise OpenMeteoRequestFailed("fail")
|
|
|
|
monkeypatch.setattr("services.local_content_service.fetch_current_weather", fail)
|
|
body = (await client.get("/v1/local/weather", params=params)).json()
|
|
assert body["result"]["code"] == ErrorType.SUCCESS.value
|
|
assert body["cached"] is True
|
|
assert body["stale"] is True
|
|
assert body["weather"]["temperature"] == 24.3
|
|
|
|
|
|
async def _async_value(value):
|
|
return value
|