"""예약 요청 폼 → 사장님 메일. ★ 이 파일이 지키는 것: - 발행된 사이트의 업장만 받는다 (place_id 를 바꿔 아무 업장에나 메일을 쏘지 못한다) - DB 에 아무것도 남기지 않는다 - 봇(허니팟·즉시 제출)은 실패로 알리지 않고 조용히 버린다 - SMTP 미설정이면 500 이 아니라 "전화로 문의" 로 답한다 """ import uuid import pytest from sqlalchemy import text from common.enums import PlaceCategory, SiteStatus from services import mail_service @pytest.fixture def sent(monkeypatch): box = [] monkeypatch.setattr(mail_service, "is_configured", lambda: True) monkeypatch.setattr(mail_service, "send", lambda **kw: box.append(kw) or True) return box async def _seed(db_engine, owner_id, *, status=SiteStatus.PUBLISHED, email="owner@example.com") -> str: place_id = uuid.uuid4() async with db_engine.begin() as conn: await conn.execute(text("UPDATE users SET email = :email WHERE user_id = :uid"), {"email": email, "uid": owner_id}) await conn.execute( text("INSERT INTO places (place_id, owner_user_id, name, category, status) " "VALUES (:pid, :uid, :name, :cat, 1)"), {"pid": place_id, "uid": owner_id, "name": "스테이,머뭄", "cat": PlaceCategory.LODGING.value}, ) await conn.execute( text("INSERT INTO sites (site_id, place_id, status) VALUES (:sid, :pid, :st)"), {"sid": uuid.uuid4(), "pid": place_id, "st": status.value}, ) return str(place_id) def _form(place_id, **over): body = { "place_id": place_id, "name": "김손님", "phone": "010-1234-5678", "email": "guest@example.com", "stay": "9월 20일 – 21일", "guests": "2명", "message": "늦게 도착합니다", "consent": True, "elapsed_ms": 9000, } body.update(over) return body async def test_sends_mail_to_owner(client, db_engine, owner_id, sent): place_id = await _seed(db_engine, owner_id) res = await client.post("/v1/site/booking-request", json=_form(place_id)) assert res.status_code == 200 assert res.json()["success"] is True assert len(sent) == 1 assert sent[0]["to"] == "owner@example.com" assert sent[0]["reply_to"] == "guest@example.com" for value in ("김손님", "010-1234-5678", "늦게 도착합니다"): assert value in sent[0]["text"] async def test_rejects_unpublished_place(client, db_engine, owner_id, sent): place_id = await _seed(db_engine, owner_id, status=SiteStatus.DRAFT) res = await client.post("/v1/site/booking-request", json=_form(place_id)) assert res.json()["success"] is False assert sent == [] async def test_unknown_place_does_not_send(client, db_engine, owner_id, sent): res = await client.post("/v1/site/booking-request", json=_form(str(uuid.uuid4()))) assert res.json()["success"] is False assert sent == [] async def test_honeypot_and_instant_submit_are_dropped(client, db_engine, owner_id, sent): place_id = await _seed(db_engine, owner_id) bot = await client.post("/v1/site/booking-request", json=_form(place_id, company="광고회사")) fast = await client.post("/v1/site/booking-request", json=_form(place_id, elapsed_ms=10)) # 봇에게는 걸렸다고 알리지 않는다 — 알리면 다음 시도가 그 조건을 피한다. assert bot.json()["success"] is True assert fast.json()["success"] is True assert sent == [] async def test_requires_consent(client, db_engine, owner_id, sent): place_id = await _seed(db_engine, owner_id) res = await client.post("/v1/site/booking-request", json=_form(place_id, consent=False)) assert res.json()["success"] is False assert sent == [] async def test_without_smtp_answers_instead_of_failing(client, db_engine, owner_id, monkeypatch): monkeypatch.setattr(mail_service, "is_configured", lambda: False) place_id = await _seed(db_engine, owner_id) res = await client.post("/v1/site/booking-request", json=_form(place_id)) assert res.status_code == 200 assert res.json()["success"] is False assert "전화" in res.json()["message"]