o2o-negosium-original/negodata/backend/tests/test_lps_sync_state.py
민헌 95086cb3f6 feat(negodata): 4단계 — 사용자 화면에서 '없음'과 '확인 못함'을 구분
마지막 단계. 차단당해 못 본 몰이 화면에서 '–'(없음)로 보여 사용자가 "쿠팡엔 더 싼 게 없구나"로
오해하던 문제를 끝낸다. 안 본 걸 없다고 말하지 않는다.

사용자 화면은 **셋으로 접는다**(lps/docs/result-states.md 3-2절). 할 수 있는 행동이
'쓴다/다시 시도/넘어간다' 뿐이라, 원인이 달라도 다음 행동이 같으면 같은 표기다:
  matched                            → 가격
  no_match · empty                   → '–'        (확인했고 없었다)
  blocked · env_blocked · unavailable → '확인 못함' (못 봤다)
운영자 화면(lps-admin)은 같은 데이터로 7상태를 그대로 본다 — 목적이 진단이라 접지 않는다.

체인 전체를 이었다:
- postgres-init/alters/2026-08-07-iilp-source-state.sql — item_internet_lowest_prices 에
  sources/partial 추가(멱등, **운영 적용 필요**)
- models.py / lps_sync_crud 읽기 계약 / lps_sync_service 미러링 / LowestPriceEntry 프로토콜
- orval 재생성(ORVAL_INPUT 으로 저장 스펙에서 — 서버 없이). 생성 diff 는 새 필드만.
- PriceUpdateModal: 가격이 없는 몰이 '못 본 몰'이면 '–' 대신 '확인 못함'.

partial 은 LPS 가 판단해 내려준 사실을 그대로 쓴다 — 화면이 '어떤 상태가 확인된 것인가'를
다시 판정하면 상태 정의가 LPS 와 negodata 두 곳으로 흩어진다.

E2E 검증(실 DB 2시나리오): by_mall 은 둘 다 naver 뿐인데 쿠팡 칸이 '확인 못함'(차단) / '–'(0건)
으로 갈린다. tsc 오류 없음(기존 xlsx 미설치 오류는 무관). negodata 97 · lps 292 passed.

> 폴더 관례상 negodata 는 인수인계 대상이나, 사용자 요청으로 이번 건도 예외 적용.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-07 11:21:56 +09:00

34 lines
1.5 KiB
Python

"""LPS 몰별 확인 상태 미러링 — '없었다'와 '못 봤다'를 화면까지 나르는 계약.
by_mall 은 **가격이 있는 몰만** 담는다. 그래서 빠진 몰이 '거기엔 없더라'인지 '거기를 못 봤다'인지는
sources/partial 에만 있다. 이 값이 중간에서 빠지면 화면은 두 경우를 구분할 방법이 없다
(실제로 그래서 차단당한 몰이 '없음'으로 보였다 — lps/docs/result-states.md).
"""
from crud.lps_sync_crud import _price_history
from router.v1.item.protocol import LowestPriceEntry
def test_read_contract_includes_source_state():
"""lps_db 읽기 계약에서 이 컬럼이 빠지면 화면까지 갈 값이 사라진다."""
cols = {c.name for c in _price_history.columns}
assert {"sources", "partial"} <= cols, cols
assert "by_mall" in cols # 함께 읽어야 '가격 있는 몰'과 '확인한 몰'을 대조할 수 있다
def test_api_entry_exposes_state_with_safe_defaults():
"""이 컬럼 추가 이전 이력(값 없음)도 화면이 깨지지 않아야 한다."""
e = LowestPriceEntry()
assert e.sources is None
assert e.partial is False, "기본이 True 면 정상 결과가 '일부 확인 못함'으로 보인다"
def test_api_entry_carries_source_state():
e = LowestPriceEntry(
lp_price=9000, success_yn=True, partial=True,
sources={"naver": {"state": "matched", "count": 40},
"coupang": {"state": "blocked", "error": "차단"}},
)
assert e.partial is True
assert e.sources["coupang"]["state"] == "blocked"