여러 줄 주석이 설명보다 경위(예전·실측·지적)를 적고 있어 읽는 사람이 결론을 찾기 어려웠다. - ts·tsx·js·mjs·css·py 478개: 여러 줄 주석은 첫 문장 한 줄로, 과거형·날짜 문장은 삭제 - 주석 위치는 TypeScript 파서·파이썬 tokenize/ast 로 찾는다 — 문자열 안의 # · /* 는 건드리지 않는다 - eslint·ts·noqa·type: ignore 같은 지시 주석은 그대로 둔다 파이썬 275개 정리 전후 AST 동일, TS 298개 주석 뺀 토큰 동일(빈 JSX 주석 10곳만 차이). site·frontend·admin tsc, site vitest 105 passed Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
83 lines
4.1 KiB
Python
83 lines
4.1 KiB
Python
"""공개 쇼케이스 목록 — 랜딩이 로그인 없이 부르는 유일한 사이트 조회."""
|
|
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
from sqlalchemy import text
|
|
|
|
from common.enums import PlaceCategory, PlaceStatus, SiteStatus
|
|
|
|
|
|
async def _publish(db_engine, owner_id, name, *, status, domain, thumb=None, minutes_ago=0):
|
|
"""places + sites 를 직접 넣는다 — 여기서 보는 건 목록 조회지 빌드 파이프라인이 아니다."""
|
|
pid, sid = uuid.uuid4(), uuid.uuid4()
|
|
async with db_engine.begin() as c:
|
|
await c.execute(
|
|
text("INSERT INTO places (place_id, owner_user_id, name, category, status, road_address, address, phone) "
|
|
"VALUES (:p,:c,:n,:cat,:st,:road,:addr,:phone)"),
|
|
{"p": pid, "c": uuid.UUID(owner_id), "n": name, "cat": PlaceCategory.LODGING.value,
|
|
"st": PlaceStatus.PUBLISHED.value,
|
|
"road": "강원특별자치도 양양군 현북면 하조대3길 12-3", "addr": "강원특별자치도 양양군 현북면 하광정리 1-2",
|
|
"phone": "033-672-0000"},
|
|
)
|
|
await c.execute(
|
|
text("INSERT INTO sites (site_id, place_id, domain, status, thumbnail_url, published_at) "
|
|
"VALUES (:s,:p,:d,:st,:t,:at)"),
|
|
{"s": sid, "p": pid, "d": domain, "st": status, "t": thumb,
|
|
"at": datetime.now(timezone.utc) - timedelta(minutes=minutes_ago)},
|
|
)
|
|
return str(pid)
|
|
|
|
|
|
async def test_발행된_사이트만_로그인_없이_보인다(client, db_engine, owner_id):
|
|
"""검증: 발행본 1개 + 미발행(DRAFT) 1개를 두고 인증 헤더 없이 부른다."""
|
|
await _publish(db_engine, owner_id, "하조대펜션", status=SiteStatus.PUBLISHED.value,
|
|
domain="hajodae", thumb="https://w4ai.o2o.kr/thumbs/hajodae.jpg")
|
|
await _publish(db_engine, owner_id, "아직펜션", status=SiteStatus.DRAFT.value, domain="notyet")
|
|
|
|
res = await client.get("/v1/showcase")
|
|
|
|
assert res.status_code == 200
|
|
items = res.json()["items"]
|
|
assert [i["name"] for i in items] == ["하조대펜션"]
|
|
assert items[0]["url"] == "/s/hajodae"
|
|
assert items[0]["thumbnail_url"] == "https://w4ai.o2o.kr/thumbs/hajodae.jpg"
|
|
assert items[0]["category"] == PlaceCategory.LODGING.value
|
|
|
|
|
|
async def test_개인정보와_내부값은_나가지_않는다(client, db_engine, owner_id):
|
|
"""검증: 응답 항목의 키를 그대로 본다."""
|
|
await _publish(db_engine, owner_id, "하조대펜션", status=SiteStatus.PUBLISHED.value, domain="hajodae")
|
|
|
|
item = (await client.get("/v1/showcase")).json()["items"][0]
|
|
|
|
assert set(item) <= {"name", "category", "region", "url", "thumbnail_url"}
|
|
assert item["region"] == "강원특별자치도 양양군"
|
|
# 번지·전화번호가 어디로도 새지 않았는지 통째로 확인한다.
|
|
body = (await client.get("/v1/showcase")).text
|
|
assert "033-672-0000" not in body
|
|
assert "하조대3길" not in body
|
|
|
|
|
|
async def test_썸네일이_없으면_키가_없다(client, db_engine, owner_id):
|
|
"""썸네일은 발행의 부수 효과라 실패할 수 있다(대표 사진이 없거나 CDN 이 죽었거나)."""
|
|
await _publish(db_engine, owner_id, "그림없는집", status=SiteStatus.PUBLISHED.value, domain="nopic")
|
|
|
|
item = (await client.get("/v1/showcase")).json()["items"][0]
|
|
|
|
assert item["name"] == "그림없는집"
|
|
assert "thumbnail_url" not in item
|
|
|
|
|
|
async def test_최신_발행순이고_limit_로_자른다(client, db_engine, owner_id):
|
|
await _publish(db_engine, owner_id, "먼저", status=SiteStatus.PUBLISHED.value, domain="first", minutes_ago=60)
|
|
await _publish(db_engine, owner_id, "나중", status=SiteStatus.PUBLISHED.value, domain="second", minutes_ago=1)
|
|
|
|
items = (await client.get("/v1/showcase")).json()["items"]
|
|
assert [i["name"] for i in items] == ["나중", "먼저"]
|
|
|
|
items = (await client.get("/v1/showcase?limit=1")).json()["items"]
|
|
assert [i["name"] for i in items] == ["나중"]
|
|
|
|
# 상한 밖은 거부한다 — 전체 명단을 한 번에 긁어가는 요청을 열어 두지 않는다.
|
|
assert (await client.get("/v1/showcase?limit=999")).status_code == 422
|