증상은 둘 다 "메일이 안 온다" / "승인했는데 홈페이지가 그대로" 로만 보인다. 서버는 정상으로
뜨고 로그에도 에러가 없어서 눈으로 원인을 못 찾는 종류다.
① 재고 채우기가 스케줄러에 **등록돼 있지 않았다.** `scheduler/jobs.py` 에 함수는 있고
`__init__.py` 주석도 "새벽에 재고를 채운다" 라고 말하는데 add_job 한 줄이 없어 한 번도
돈 적이 없다. 09:00 발송만 돌고 보낼 글은 0건이었다 — 지금 DB 의 254건은 전부 화면의
[지금 생성하기] 로 손으로 만든 것이다.
→ 04:10 KST 등록(발송보다 앞서야 그날 아침에 나갈 재고가 있다)
② 승인 뒤 재발행이 죽는다. `post_service._enqueue_build` 가 requested_by 에
"blog-approval" 이라는 **라벨**을 넣었고 `build_service._log` 가 그걸 uuid.UUID() 에
넣다 ValueError 를 던졌다. 하필 _log 는 사이트를 다 구운 **뒤**, sites.status 를
PUBLISHED 로 찍기 **전**에 불린다 — 굽기는 끝났는데 발행만 안 된 채 3회 재시도 후 DEAD.
실측: BUILD 잡 5건(2026-09-23~09-30)이 전부 이 원인이고 전부 미니블로그 승인분이었다.
→ 호출부는 사장님 ID 를 넣고, 파서(_actor_uuid)는 모양이 틀리면 기록만 비우고 진행한다.
감사 기록 한 줄이 발행을 막는 것은 순서가 뒤집힌 것이다. rollback_service 도 같은 파서.
- scheduler/__init__: blog-drafts 등록 + 왜 빠져 있었는지
- services/build_service: _actor_uuid 신설, _log 가 그것만 쓴다
- services/post_service: requested_by = str(owner_user_id)
- services/rollback_service: 같은 파서 재사용
테스트 7건 추가(build 4 · scheduler 3), 전부 통과.
test_build_publish.py 의 기존 실패 13건은 변동 없음 — 원본으로 되돌려 측정해 확인했다
(원본 13 failed/2 passed, 변경 후 13 failed/6 passed). 그 13건은 컨테이너 테스트 DB
쪽 문제다(로그: lease 갱신 실패 InvalidCatalogNameError) — 이 변경과 무관하고 미해결로 남긴다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
45 lines
2.0 KiB
Python
45 lines
2.0 KiB
Python
"""스케줄러에 무엇이 등록돼 있나.
|
|
|
|
★★ 이 파일이 있는 이유: `sweep_blog_drafts` 가 **등록돼 있지 않았다**(실측 2026-09-30).
|
|
`scheduler/jobs.py` 에 함수는 있고 주석도 "새벽에 재고를 채운다" 라고 말하는데
|
|
`add_job` 한 줄이 없어 한 번도 돈 적이 없다. 증상은 "미니블로그 메일이 안 온다" 로만
|
|
보여서 발송 쪽을 보게 된다 — 실제로는 **보낼 글이 0건**이었다.
|
|
잡 하나가 빠져도 서버는 정상으로 뜨고 로그에도 에러가 없다. 그래서 세는 것이다.
|
|
"""
|
|
import pytest
|
|
|
|
import scheduler
|
|
|
|
|
|
@pytest.fixture
|
|
def started(monkeypatch):
|
|
monkeypatch.setenv("SCHEDULER_ENABLED", "1")
|
|
# 실제로 돌리지 않는다 — 등록만 본다.
|
|
monkeypatch.setattr("apscheduler.schedulers.asyncio.AsyncIOScheduler.start", lambda self: None)
|
|
scheduler._scheduler = None
|
|
scheduler.start_scheduler()
|
|
yield scheduler._scheduler
|
|
scheduler._scheduler = None
|
|
|
|
|
|
def test_미니블로그는_생성과_발송이_둘_다_등록된다(started):
|
|
"""★ 발송만 있으면 보낼 글이 영원히 0건이다."""
|
|
ids = {j.id for j in started.get_jobs()}
|
|
assert "blog-drafts" in ids, "재고 채우기가 등록되지 않았다 — 자동 생성이 안 돈다"
|
|
assert "blog-mail" in ids
|
|
|
|
|
|
def test_재고_채우기가_발송보다_앞선다(started):
|
|
"""그날 아침에 나갈 글이 있으려면 생성이 먼저여야 한다."""
|
|
by_id = {j.id: j for j in started.get_jobs()}
|
|
hour = lambda j: int(str(by_id[j].trigger.fields[by_id[j].trigger.FIELD_NAMES.index("hour")]))
|
|
assert hour("blog-drafts") < hour("blog-mail")
|
|
|
|
|
|
def test_스위치가_꺼지면_아무것도_등록하지_않는다(monkeypatch):
|
|
"""키 없는 환경에 돌지도 않을 잡을 만들어 두지 않는다."""
|
|
monkeypatch.setenv("SCHEDULER_ENABLED", "0")
|
|
scheduler._scheduler = None
|
|
scheduler.start_scheduler()
|
|
assert scheduler._scheduler is None
|