Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
66908cb5c6
@ -100,7 +100,8 @@ KAKAO_BOT_ID=
|
|||||||
KAKAO_BOT_REST_API_KEY=
|
KAKAO_BOT_REST_API_KEY=
|
||||||
# 1 이면 개발 채널로 보낸다(봇 ID 뒤에 "!"). 운영 채널이면 0.
|
# 1 이면 개발 채널로 보낸다(봇 ID 뒤에 "!"). 운영 채널이면 0.
|
||||||
KAKAO_EVENT_DEV=0
|
KAKAO_EVENT_DEV=0
|
||||||
# 미니블로그 승인 알림을 메일에 더해 연결된 카톡으로도 보낸다(Event API).
|
# 카톡이 연결된 사장님에게는 미니블로그 승인 알림을 메일 대신 카톡으로 보낸다(Event API).
|
||||||
|
# 카톡이 실패했을 때만 메일로 대체하고, 연결이 없으면 예전처럼 메일이다.
|
||||||
# ★ 오픈빌더에 이벤트 블록(스킬 연결)을 만들고 배포하기 전에는 켜지 않는다 — 켜면
|
# ★ 오픈빌더에 이벤트 블록(스킬 연결)을 만들고 배포하기 전에는 켜지 않는다 — 켜면
|
||||||
# 사장님 카톡에 빈 말풍선이 간다. 기본 꺼짐.
|
# 사장님 카톡에 빈 말풍선이 간다. 기본 꺼짐.
|
||||||
KAKAO_APPROVAL_PUSH_ENABLED=0
|
KAKAO_APPROVAL_PUSH_ENABLED=0
|
||||||
|
|||||||
@ -471,7 +471,7 @@ https://<발행호스트>/v1/agent/kakao/webhook
|
|||||||
|
|
||||||
| | 결정 |
|
| | 결정 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| 발송 | **카톡과 메일 둘 다.** 카톡이 연결돼 있어도 메일을 같이 보낸다 — 카톡 발송은 채널 친구가 아니거나 차단했으면 실패하므로 메일이 누락을 막는다 |
|
| 발송 | **카톡이 연결돼 있으면 카톡으로만 보내고, 카톡이 실패했을 때만 메일로 대체한다**(2026-09-30 변경 — 처음엔 '카톡과 메일 둘 다'였는데 같은 알림이 두 통 가는 게 번거롭다는 지시. 카톡만 보내면 채널 친구 아님·차단·Event API 장애인 날 알림이 통째로 사라져 실패 때만 메일로 받쳤다). 연결이 없거나 스위치가 꺼져 있으면 예전처럼 메일. 카톡도 메일도 못 보내면 SENT 로 표시하지 않아 다음 스윕이 다시 시도하고 '승인 알림보내기' 는 실패로 답한다 |
|
||||||
| 승인 방식 | 메시지의 **[승인] [수정] 인라인 버튼** + 누른 사람이 **연결된 본인인지·그 글이 본인 가게 것인지 서버가 확인**. 링크가 없어 메신저 미리보기가 먼저 열어 승인되는 문제가 처음부터 없다. 승인은 기존 `PostService.approve_by_owner` 를 그대로 탄다 |
|
| 승인 방식 | 메시지의 **[승인] [수정] 인라인 버튼** + 누른 사람이 **연결된 본인인지·그 글이 본인 가게 것인지 서버가 확인**. 링크가 없어 메신저 미리보기가 먼저 열어 승인되는 문제가 처음부터 없다. 승인은 기존 `PostService.approve_by_owner` 를 그대로 탄다 |
|
||||||
| 진행 | **1단계(이 절)**: 클라이언트 + 테스트 발송으로 규격 확인. 2단계: 버튼 응답·승인 처리·발송 연결 |
|
| 진행 | **1단계(이 절)**: 클라이언트 + 테스트 발송으로 규격 확인. 2단계: 버튼 응답·승인 처리·발송 연결 |
|
||||||
|
|
||||||
@ -529,7 +529,7 @@ cd solution/backend && .venv/bin/python scripts/kakao_event_send_test.py --key <
|
|||||||
## 2-1 진행 — 발송과 메시지 그리기 (2026-09-29)
|
## 2-1 진행 — 발송과 메시지 그리기 (2026-09-29)
|
||||||
|
|
||||||
```
|
```
|
||||||
blog_jobs._send_one 메일 + 카톡 Event API(params: post_id, edit_token) — 하나라도 나가면 SENT
|
blog_jobs._send_one 카톡 Event API(params: post_id, edit_token) — 실패했을 때만 메일로 대체, 나가면 SENT
|
||||||
오픈빌더 이벤트 블록 스킬 데이터 응답 → 우리 웹훅(POST /v1/agent/kakao/webhook)
|
오픈빌더 이벤트 블록 스킬 데이터 응답 → 우리 웹훅(POST /v1/agent/kakao/webhook)
|
||||||
kakao_bot._handle userRequest.params.post_id 가 있으면 승인 알림 요청으로 처리
|
kakao_bot._handle userRequest.params.post_id 가 있으면 승인 알림 요청으로 처리
|
||||||
channel.approval_notice 연결된 본인 가게의 글일 때만 본문 + [수정하기] 링크
|
channel.approval_notice 연결된 본인 가게의 글일 때만 본문 + [수정하기] 링크
|
||||||
@ -542,8 +542,8 @@ channel.approval_notice 연결된 본인 가게의 글일 때만 본문 + [수
|
|||||||
- [수정하기] 는 메일의 '고쳐서 올리려면' 과 **같은 일회용 코드**다(`/v1/site/post/edit?t=`).
|
- [수정하기] 는 메일의 '고쳐서 올리려면' 과 **같은 일회용 코드**다(`/v1/site/post/edit?t=`).
|
||||||
평문은 발송 시점에만 알아서 Event API `params.edit_token` 으로 넘긴다 — 어느 쪽이든 먼저
|
평문은 발송 시점에만 알아서 Event API `params.edit_token` 으로 넘긴다 — 어느 쪽이든 먼저
|
||||||
누른 쪽이 쓴다. 코드는 카카오 서버를 지나가므로 로그에는 params 의 **키만** 남기고 값은 남기지 않는다.
|
누른 쪽이 쓴다. 코드는 카카오 서버를 지나가므로 로그에는 params 의 **키만** 남기고 값은 남기지 않는다.
|
||||||
- 메일이 나갔으면 카톡보다 먼저 SENT 로 표시한다(웹훅이 곧바로 글을 읽는다). 카톡만 나가는
|
- 카톡 발송이 성공한 **뒤에** SENT 로 표시한다(카톡이 실패하면 메일 발송 뒤). 그 사이에 웹훅이 이 글을 읽어도 되는 이유는 웹훅이
|
||||||
경우는 발송 성공 뒤에 표시하고, 웹훅은 REVIEWED 글도 읽는다.
|
REVIEWED 글도 읽고, 사장님이 버튼을 누르기까지의 시간이 그 간격보다 훨씬 길어서다.
|
||||||
- 링크 버튼은 `textCard` 로 본문(`simpleText`)과 따로 둔다 — 카드 설명 길이 제한에 글 문구가 걸리지 않게.
|
- 링크 버튼은 `textCard` 로 본문(`simpleText`)과 따로 둔다 — 카드 설명 길이 제한에 글 문구가 걸리지 않게.
|
||||||
|
|
||||||
### 콘솔에서 바꿔야 하는 것
|
### 콘솔에서 바꿔야 하는 것
|
||||||
|
|||||||
@ -130,11 +130,13 @@
|
|||||||
수정 링크로 할 수 있는 일은 **그 글 한 건의 편집·승인**뿐이다(day-pass 토큰도 `user_id`
|
수정 링크로 할 수 있는 일은 **그 글 한 건의 편집·승인**뿐이다(day-pass 토큰도 `user_id`
|
||||||
까지만 담아, 그 사장님의 다른 글은 못 건드리지 않는다 — `PostService.get_post` 가
|
까지만 담아, 그 사장님의 다른 글은 못 건드리지 않는다 — `PostService.get_post` 가
|
||||||
`place_id` 불일치를 걸러낸다)
|
`place_id` 불일치를 걸러낸다)
|
||||||
- **카톡 병행**(2026-09-29 결정: 카톡과 메일 **둘 다**): 사장님이 카카오톡을 연결했고
|
- **카톡 알림**(2026-09-30 변경: 연결돼 있으면 **카톡으로만, 실패했을 때만 메일로 대체**): 사장님이
|
||||||
`KAKAO_APPROVAL_PUSH_ENABLED=1` 이면 메일에 더해 Event API 로도 보낸다
|
카카오톡을 연결했고 `KAKAO_APPROVAL_PUSH_ENABLED=1` 이면 메일 대신 Event API 로 보낸다
|
||||||
(`blog_jobs._push_kakao`, 규격·콘솔 준비는 `docs/AGENT.md` 5단계). 카톡은 채널 친구가
|
(`blog_jobs._push_kakao`, 규격·콘솔 준비는 `docs/AGENT.md` 5단계). 처음(2026-09-29)엔 '둘 다'
|
||||||
아니거나 차단했으면 실패하므로 메일을 빼지 않는다. **하나라도 나갔으면 SENT**, 아무 데도
|
였는데 같은 알림이 두 통 가는 게 번거롭다는 지시로 바꿨다. 카톡만 보내면 채널 친구 아님·차단·
|
||||||
안 나갔으면 SENT 로 표시하지 않아 다음 스윕이 다시 시도한다. 글 본문과 [수정하기] 링크는
|
Event API 장애인 날 알림이 통째로 사라져서 **카톡이 실패했을 때만 메일로 대체**한다. 연결이
|
||||||
|
없거나 스위치가 꺼져 있으면 예전처럼 메일이다. 카톡도 메일도 못 보내면 SENT 로 표시하지 않아
|
||||||
|
다음 스윕이 다시 시도하고 '승인 알림보내기' 는 실패로 답한다. 글 본문과 [수정하기] 링크는
|
||||||
오픈빌더 이벤트 블록의 스킬(우리 웹훅)이 그린다 — params 로 글 ID 와 수정용 일회용 코드가
|
오픈빌더 이벤트 블록의 스킬(우리 웹훅)이 그린다 — params 로 글 ID 와 수정용 일회용 코드가
|
||||||
가고, 웹훅이 **연결된 본인 가게의 글인지 다시 확인한 뒤에만** 본문을 준다
|
가고, 웹훅이 **연결된 본인 가게의 글인지 다시 확인한 뒤에만** 본문을 준다
|
||||||
(`channel.approval_notice`).
|
(`channel.approval_notice`).
|
||||||
|
|||||||
@ -49,7 +49,8 @@ class AgentConfig(BaseSettings):
|
|||||||
# "1" 이면 봇 ID 뒤에 "!" 를 붙여 **개발 채널**로 보낸다(운영 채널과 요청 주소가 다르다).
|
# "1" 이면 봇 ID 뒤에 "!" 를 붙여 **개발 채널**로 보낸다(운영 채널과 요청 주소가 다르다).
|
||||||
# ★ KAKAO_BOT_ID 자체를 고쳐 쓰지 않는다 — 웹훅이 그 값으로 요청의 bot.id 를 대조한다.
|
# ★ KAKAO_BOT_ID 자체를 고쳐 쓰지 않는다 — 웹훅이 그 값으로 요청의 bot.id 를 대조한다.
|
||||||
KAKAO_EVENT_DEV: str = "0"
|
KAKAO_EVENT_DEV: str = "0"
|
||||||
# 미니블로그 승인 알림을 메일에 더해 카톡으로도 보낸다. ★ 기본 꺼짐 — 오픈빌더에 이벤트
|
# 카톡이 연결된 사장님에게는 미니블로그 승인 알림을 메일 대신 카톡으로 보낸다(카톡이 실패했을
|
||||||
|
# 때만 메일로 대체, 연결이 없으면 메일). ★ 기본 꺼짐 — 오픈빌더에 이벤트
|
||||||
# 블록(스킬 연결)과 배포가 끝나기 전에 켜면 사장님 카톡에 빈 말풍선이 간다.
|
# 블록(스킬 연결)과 배포가 끝나기 전에 켜면 사장님 카톡에 빈 말풍선이 간다.
|
||||||
KAKAO_APPROVAL_PUSH_ENABLED: str = "0"
|
KAKAO_APPROVAL_PUSH_ENABLED: str = "0"
|
||||||
# 오픈빌더에 정의한 이벤트 이름. 말풍선을 그리는 블록이 이 이름에 걸려 있다.
|
# 오픈빌더에 정의한 이벤트 이름. 말풍선을 그리는 블록이 이 이름에 걸려 있다.
|
||||||
|
|||||||
@ -267,45 +267,46 @@ async def _push_kakao(channel_user_key: str, post, edit_token: str) -> bool:
|
|||||||
|
|
||||||
|
|
||||||
async def _send_one(place, user, post) -> bool:
|
async def _send_one(place, user, post) -> bool:
|
||||||
"""토큰 발급 → 메일·카톡 발송 → 하나라도 나갔으면 SENT 로 표시. 아무 데도 안 나갔으면
|
"""토큰 발급 → 발송 → 나갔으면 SENT 로 표시. 아무 데도 안 나갔으면 DB 를 안 건드린다
|
||||||
DB 를 안 건드린다(다시 시도할 길을 막지 않는다).
|
(다시 시도할 길을 막지 않는다).
|
||||||
|
|
||||||
★ 카톡과 메일은 **둘 다** 보낸다(2026-09-29 결정). 카톡은 채널 친구가 아니거나 차단했으면
|
★ 카톡이 연결돼 있으면 **카톡으로만** 보내고, 카톡이 **실패했을 때만** 메일로 대체한다
|
||||||
실패하므로, 카톡이 붙어도 메일을 빼지 않는다."""
|
(2026-09-30). 처음(2026-09-29)엔 '둘 다'였는데 같은 알림이 두 통 가는 게 번거롭다는 지시로
|
||||||
|
바꿨고, 카톡만 보내면 채널 친구가 아니거나 차단했거나 Event API 가 죽은 날 알림이 통째로
|
||||||
|
사라져서 실패 때만 메일로 받치게 했다. 연결이 없거나 스위치가 꺼져 있으면 예전처럼 메일이다.
|
||||||
|
★ 카톡도 메일도 못 보내면 SENT 로 표시하지 않는다 — 다음 스윕이 다시 시도하고 '승인 알림보내기'
|
||||||
|
는 실패로 답한다."""
|
||||||
token, token_hash, expires = blog_service.issue_token()
|
token, token_hash, expires = blog_service.issue_token()
|
||||||
# ★ 수정 링크도 일회용 코드다. 예전에는 여기 빌더 액세스 토큰을 통짜로 실었고,
|
# ★ 수정 링크도 일회용 코드다. 예전에는 여기 빌더 액세스 토큰을 통짜로 실었고,
|
||||||
# 메일 전달 한 번이 자정까지의 권한 양도였다(migrations/0023).
|
# 메일 전달 한 번이 자정까지의 권한 양도였다(migrations/0023).
|
||||||
edit_token, edit_hash, _edit_expires = blog_service.issue_token()
|
edit_token, edit_hash, _edit_expires = blog_service.issue_token()
|
||||||
|
|
||||||
mail_ok = False
|
|
||||||
if _can_mail(place, user):
|
|
||||||
body = _mail_body(
|
|
||||||
place_name=place.name, post=post, user=user,
|
|
||||||
origin=_app_origin(), approve_token=token, edit_token=edit_token,
|
|
||||||
)
|
|
||||||
mail_ok = mail_service.send(to=_notify_address(place, user), subject=f"[{place.name}] 이번 글 올릴까요?", text=body)
|
|
||||||
|
|
||||||
async def mark_sent():
|
async def mark_sent():
|
||||||
await DB_SESSION_MNG.execute_lambda_run(
|
await DB_SESSION_MNG.execute_lambda_run(
|
||||||
[place_posts.DBType()],
|
[place_posts.DBType()],
|
||||||
[lambda s, pid=post.post_id, h=token_hash, e=expires, eh=edit_hash: _crud.mark_sent(s, pid, h, e, eh)],
|
[lambda s, pid=post.post_id, h=token_hash, e=expires, eh=edit_hash: _crud.mark_sent(s, pid, h, e, eh)],
|
||||||
)
|
)
|
||||||
|
|
||||||
# ★ 메일이 나갔으면 카톡보다 **먼저** SENT 로 표시한다. 이벤트가 나가자마자 웹훅이 이 글을
|
|
||||||
# 읽는데, 그때 수정 코드 해시가 저장돼 있으면 [수정하기] 가 바로 먹는다. 카톡만 나가는
|
|
||||||
# 경우는 발송이 성공해야 표시할 수 있어 그 뒤에 한다 — 사장님이 버튼을 누르기까지의
|
|
||||||
# 시간이 그 간격보다 훨씬 길다(웹훅은 REVIEWED 글도 읽는다).
|
|
||||||
if mail_ok:
|
|
||||||
await mark_sent()
|
|
||||||
|
|
||||||
kakao_key = await _kakao_target(user)
|
kakao_key = await _kakao_target(user)
|
||||||
if kakao_key is None:
|
if kakao_key is not None:
|
||||||
return mail_ok
|
# 카톡 발송이 성공해야 SENT 로 표시할 수 있어 그 뒤에 한다. 이벤트가 나가자마자 웹훅이 이
|
||||||
|
# 글을 읽어도 되는 이유: 웹훅은 REVIEWED 글도 읽고, 사장님이 버튼을 누르기까지의 시간이
|
||||||
|
# 이 간격보다 훨씬 길다.
|
||||||
|
if await _push_kakao(kakao_key, post, edit_token):
|
||||||
|
await mark_sent()
|
||||||
|
return True
|
||||||
|
LOG.w(f"[blog] post={post.post_id} 카톡 승인 알림이 안 나가 메일로 대체한다")
|
||||||
|
|
||||||
kakao_ok = await _push_kakao(kakao_key, post, edit_token)
|
if not _can_mail(place, user):
|
||||||
if kakao_ok and not mail_ok:
|
return False
|
||||||
await mark_sent()
|
body = _mail_body(
|
||||||
return mail_ok or kakao_ok
|
place_name=place.name, post=post, user=user,
|
||||||
|
origin=_app_origin(), approve_token=token, edit_token=edit_token,
|
||||||
|
)
|
||||||
|
if not mail_service.send(to=_notify_address(place, user), subject=f"[{place.name}] 이번 글 올릴까요?", text=body):
|
||||||
|
return False
|
||||||
|
await mark_sent()
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
async def send_reviewed() -> int:
|
async def send_reviewed() -> int:
|
||||||
@ -327,7 +328,8 @@ async def send_reviewed() -> int:
|
|||||||
if not target:
|
if not target:
|
||||||
continue
|
continue
|
||||||
place, user = target
|
place, user = target
|
||||||
# 메일도 카톡도 보낼 수 없는 사장님은 건너뛴다. 하나라도 되면 _send_one 이 갈래를 정한다.
|
# 메일도 카톡도 보낼 수 없는 사장님은 건너뛴다. 어느 쪽으로 보낼지는 _send_one 이 정한다
|
||||||
|
# (카톡이 연결돼 있으면 카톡, 실패했을 때만 메일 — 연결이 없으면 메일).
|
||||||
if not _can_mail(place, user) and await _kakao_target(user) is None:
|
if not _can_mail(place, user) and await _kakao_target(user) is None:
|
||||||
continue
|
continue
|
||||||
if await _send_one(place, user, post):
|
if await _send_one(place, user, post):
|
||||||
|
|||||||
@ -459,8 +459,8 @@ async def test_send_now_cannot_be_triggered_for_someone_elses_place(client, db_e
|
|||||||
assert res.json()["result"]["success"] is False
|
assert res.json()["result"]["success"] is False
|
||||||
|
|
||||||
|
|
||||||
async def _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypatch, login_id, name):
|
async def _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypatch, login_id, name, link=True):
|
||||||
"""발행된 사이트 + 메일 주소 + 카톡이 연결된 사장님. 카톡 Event API 는 켜 둔다."""
|
"""발행된 사이트 + 메일 주소 + (link=True 면) 카톡이 연결된 사장님. 카톡 Event API 는 켜 둔다."""
|
||||||
from services import kakao_link_service
|
from services import kakao_link_service
|
||||||
|
|
||||||
monkeypatch.setenv("KAKAO_CHANNEL_PUBLIC_ID", "_testCh")
|
monkeypatch.setenv("KAKAO_CHANNEL_PUBLIC_ID", "_testCh")
|
||||||
@ -477,7 +477,8 @@ async def _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypa
|
|||||||
)
|
)
|
||||||
await conn.execute(text("UPDATE users SET email = :e WHERE id = :id"), {"e": f"{login_id}@example.com", "id": login_id})
|
await conn.execute(text("UPDATE users SET email = :e WHERE id = :id"), {"e": f"{login_id}@example.com", "id": login_id})
|
||||||
user_id = (await conn.execute(text("SELECT user_id FROM users WHERE id = :id"), {"id": login_id})).scalar_one()
|
user_id = (await conn.execute(text("SELECT user_id FROM users WHERE id = :id"), {"id": login_id})).scalar_one()
|
||||||
await kakao_link_service.redeem((await kakao_link_service.issue_code(user_id))["code"], f"speaker-{login_id}")
|
if link:
|
||||||
|
await kakao_link_service.redeem((await kakao_link_service.issue_code(user_id))["code"], f"speaker-{login_id}")
|
||||||
return place_id, f"speaker-{login_id}", h
|
return place_id, f"speaker-{login_id}", h
|
||||||
|
|
||||||
|
|
||||||
@ -496,8 +497,9 @@ def _record_kakao_send(monkeypatch, *, fail=False):
|
|||||||
return calls
|
return calls
|
||||||
|
|
||||||
|
|
||||||
async def test_send_reviewed_also_pushes_to_the_linked_kakao(client, db_engine, auth_headers, monkeypatch):
|
async def test_send_reviewed_sends_only_kakao_when_linked(client, db_engine, auth_headers, monkeypatch):
|
||||||
"""승인 알림은 메일에 더해 연결된 카카오톡으로도 나간다(2026-09-29, 결정: 카톡과 메일 둘 다).
|
"""카톡이 연결돼 있으면 승인 알림은 카톡으로만 나가고 메일은 안 나간다(2026-09-30 변경 —
|
||||||
|
처음엔 '둘 다'였는데 사장님에게 같은 알림이 두 통 가는 게 번거롭다고 했다).
|
||||||
카톡에는 글 ID 와 수정 링크용 일회용 코드가 params 로 실려, 웹훅이 메시지를 그린다."""
|
카톡에는 글 ID 와 수정 링크용 일회용 코드가 params 로 실려, 웹훅이 메시지를 그린다."""
|
||||||
from services import blog_jobs, mail_service
|
from services import blog_jobs, mail_service
|
||||||
|
|
||||||
@ -511,7 +513,7 @@ async def test_send_reviewed_also_pushes_to_the_linked_kakao(client, db_engine,
|
|||||||
sent = await blog_jobs.send_reviewed()
|
sent = await blog_jobs.send_reviewed()
|
||||||
|
|
||||||
assert sent == 1
|
assert sent == 1
|
||||||
assert len(mails) == 1
|
assert mails == []
|
||||||
assert len(kakao) == 1
|
assert len(kakao) == 1
|
||||||
assert kakao[0]["key"] == speaker
|
assert kakao[0]["key"] == speaker
|
||||||
assert kakao[0]["event"] == "post_approval"
|
assert kakao[0]["event"] == "post_approval"
|
||||||
@ -520,8 +522,9 @@ async def test_send_reviewed_also_pushes_to_the_linked_kakao(client, db_engine,
|
|||||||
assert await _status(db_engine, post_id) == PostStatus.SENT.value
|
assert await _status(db_engine, post_id) == PostStatus.SENT.value
|
||||||
|
|
||||||
|
|
||||||
async def test_a_failed_kakao_push_does_not_block_the_mail(client, db_engine, auth_headers, monkeypatch):
|
async def test_a_failed_kakao_push_falls_back_to_mail(client, db_engine, auth_headers, monkeypatch):
|
||||||
"""카톡은 채널 친구가 아니거나 차단했으면 실패한다 — 그래서 메일도 같이 보낸다."""
|
"""카톡은 채널 친구가 아니거나 차단했거나 Event API 가 죽으면 실패한다 — 그때만 메일로 대체한다
|
||||||
|
(2026-09-30: 연결돼 있으면 카톡만, 실패했을 때만 메일). 알림이 통째로 사라지지 않게."""
|
||||||
from services import blog_jobs, mail_service
|
from services import blog_jobs, mail_service
|
||||||
|
|
||||||
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
||||||
@ -587,12 +590,13 @@ async def test_kakao_push_is_off_unless_switched_on(client, db_engine, auth_head
|
|||||||
assert kakao == []
|
assert kakao == []
|
||||||
|
|
||||||
|
|
||||||
async def test_send_now_also_pushes_to_kakao(client, db_engine, auth_headers, monkeypatch):
|
async def test_send_now_sends_only_kakao_when_linked(client, db_engine, auth_headers, monkeypatch):
|
||||||
"""'승인 알림보내기' 버튼도 같은 경로다 — 메일만 가고 카톡은 빠지면 안 된다."""
|
"""'승인 알림보내기' 버튼도 같은 경로다 — 연결돼 있으면 카톡으로만 간다."""
|
||||||
from services import mail_service
|
from services import mail_service
|
||||||
|
|
||||||
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
||||||
monkeypatch.setattr(mail_service, "send", lambda **kwargs: True)
|
mails = []
|
||||||
|
monkeypatch.setattr(mail_service, "send", lambda **kwargs: mails.append(kwargs) or True)
|
||||||
kakao = _record_kakao_send(monkeypatch)
|
kakao = _record_kakao_send(monkeypatch)
|
||||||
place_id, _speaker, h = await _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypatch, "kakaosend6", "카톡즉시펜션")
|
place_id, _speaker, h = await _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypatch, "kakaosend6", "카톡즉시펜션")
|
||||||
await _seed_post(db_engine, place_id, scheduled=date.today())
|
await _seed_post(db_engine, place_id, scheduled=date.today())
|
||||||
@ -601,6 +605,61 @@ async def test_send_now_also_pushes_to_kakao(client, db_engine, auth_headers, mo
|
|||||||
|
|
||||||
assert res.json()["result"]["success"] is True
|
assert res.json()["result"]["success"] is True
|
||||||
assert len(kakao) == 1
|
assert len(kakao) == 1
|
||||||
|
assert mails == []
|
||||||
|
|
||||||
|
|
||||||
|
async def test_send_now_falls_back_to_mail_when_kakao_fails(client, db_engine, auth_headers, monkeypatch):
|
||||||
|
"""'승인 알림보내기' 도 같은 정책이다 — 카톡이 실패하면 메일로 나가 사장님 화면에는 성공으로 보인다."""
|
||||||
|
from services import mail_service
|
||||||
|
|
||||||
|
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
||||||
|
mails = []
|
||||||
|
monkeypatch.setattr(mail_service, "send", lambda **kwargs: mails.append(kwargs) or True)
|
||||||
|
kakao = _record_kakao_send(monkeypatch, fail=True)
|
||||||
|
place_id, _speaker, h = await _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypatch, "kakaosend7", "카톡즉시실패펜션")
|
||||||
|
await _seed_post(db_engine, place_id, scheduled=date.today())
|
||||||
|
|
||||||
|
res = await client.post(f"/v1/place/{place_id}/post/send-now", headers=h)
|
||||||
|
|
||||||
|
assert res.json()["result"]["success"] is True
|
||||||
|
assert len(kakao) == 1
|
||||||
|
assert len(mails) == 1
|
||||||
|
|
||||||
|
|
||||||
|
async def test_send_now_reports_failure_when_both_kakao_and_mail_fail(client, db_engine, auth_headers, monkeypatch):
|
||||||
|
"""둘 다 안 나갔으면 사장님 화면에 실패로 보여야 한다 — 성공처럼 보이면 알림이 안 간 걸 모른다."""
|
||||||
|
from services import mail_service
|
||||||
|
|
||||||
|
monkeypatch.setattr(mail_service, "is_configured", lambda: False)
|
||||||
|
_record_kakao_send(monkeypatch, fail=True)
|
||||||
|
place_id, _speaker, h = await _publish_site_and_link_kakao(client, db_engine, auth_headers, monkeypatch, "kakaosend9", "카톡메일둘다실패펜션")
|
||||||
|
post_id = await _seed_post(db_engine, place_id, scheduled=date.today())
|
||||||
|
|
||||||
|
res = await client.post(f"/v1/place/{place_id}/post/send-now", headers=h)
|
||||||
|
|
||||||
|
assert res.json()["result"]["success"] is False
|
||||||
|
assert await _status(db_engine, post_id) == PostStatus.REVIEWED.value
|
||||||
|
|
||||||
|
|
||||||
|
async def test_mail_is_still_sent_when_kakao_is_not_linked(client, db_engine, auth_headers, monkeypatch):
|
||||||
|
"""카톡을 연결하지 않은 사장님은 스위치가 켜져 있어도 예전처럼 메일을 받는다."""
|
||||||
|
from services import blog_jobs, mail_service
|
||||||
|
|
||||||
|
monkeypatch.setattr(mail_service, "is_configured", lambda: True)
|
||||||
|
mails = []
|
||||||
|
monkeypatch.setattr(mail_service, "send", lambda **kwargs: mails.append(kwargs) or True)
|
||||||
|
kakao = _record_kakao_send(monkeypatch)
|
||||||
|
place_id, _speaker, _h = await _publish_site_and_link_kakao(
|
||||||
|
client, db_engine, auth_headers, monkeypatch, "kakaosend8", "카톡미연결펜션", link=False,
|
||||||
|
)
|
||||||
|
post_id = await _seed_post(db_engine, place_id, scheduled=date.today())
|
||||||
|
|
||||||
|
sent = await blog_jobs.send_reviewed()
|
||||||
|
|
||||||
|
assert sent == 1
|
||||||
|
assert len(mails) == 1
|
||||||
|
assert kakao == []
|
||||||
|
assert await _status(db_engine, post_id) == PostStatus.SENT.value
|
||||||
|
|
||||||
|
|
||||||
async def _day_pass_headers(db_engine, login_id: str) -> dict:
|
async def _day_pass_headers(db_engine, login_id: str) -> dict:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user