테스트 강화 및 코드 품질 개선

This commit is contained in:
2025-12-17 00:01:46 +09:00
parent 37a150bd0d
commit 00c57ddd32
51 changed files with 10670 additions and 217 deletions

View File

@@ -4,6 +4,11 @@ import time
import requests
from .common import logger
from .constants import (
TELEGRAM_MAX_MESSAGE_LENGTH,
TELEGRAM_RATE_LIMIT_DELAY,
TELEGRAM_REQUEST_TIMEOUT,
)
__all__ = ["send_telegram", "send_telegram_with_retry", "report_error", "send_startup_test_message"]
@@ -51,10 +56,29 @@ def send_telegram_with_retry(
return False
def send_telegram(token: str, chat_id: str, text: str, add_thread_prefix: bool = True, parse_mode: str = None):
def send_telegram(
token: str,
chat_id: str,
text: str,
add_thread_prefix: bool = True,
parse_mode: str = None,
max_length: int = TELEGRAM_MAX_MESSAGE_LENGTH,
):
"""
텔레그램 메시지를 한 번 전송합니다. 실패 시 예외를 발생시킵니다.
WARNING: 이 함수는 예외 처리가 없으므로, 프로덕션에서는 send_telegram_with_retry() 사용 권장
텔레그램 메시지를 전송합니다 (자동 분할 지원).
Args:
token: 텔레그램 봇 토큰
chat_id: 채팅 ID
text: 메시지 내용
add_thread_prefix: 스레드 이름 prefix 추가 여부
parse_mode: HTML/Markdown 파싱 모드
max_length: 최대 메시지 길이 (Telegram 제한 4096자, 안전하게 4000자)
Note:
- 메시지가 max_length를 초과하면 자동으로 분할하여 전송합니다.
- 실패 시 예외를 발생시킵니다.
- 프로덕션에서는 send_telegram_with_retry() 사용 권장
"""
if add_thread_prefix:
thread_name = threading.current_thread().name
@@ -67,23 +91,56 @@ def send_telegram(token: str, chat_id: str, text: str, add_thread_prefix: bool =
payload_text = text
url = f"https://api.telegram.org/bot{token}/sendMessage"
payload = {"chat_id": chat_id, "text": payload_text}
if parse_mode:
payload["parse_mode"] = parse_mode
try:
# ⚠️ 타임아웃 증가 (20초): SSL handshake 느림 대비
resp = requests.post(url, json=payload, timeout=20)
resp.raise_for_status() # 2xx 상태 코드가 아니면 HTTPError 발생
logger.debug("텔레그램 메시지 전송 성공: %s", text[:80])
# ✅ 메시지 길이 확인 및 분할
if len(payload_text) <= max_length:
# 단일 메시지 전송
payload = {"chat_id": chat_id, "text": payload_text}
if parse_mode:
payload["parse_mode"] = parse_mode
try:
resp = requests.post(url, json=payload, timeout=TELEGRAM_REQUEST_TIMEOUT)
resp.raise_for_status()
logger.debug("텔레그램 메시지 전송 성공: %s", payload_text[:80])
return True
except requests.exceptions.Timeout as e:
logger.warning("텔레그램 타임아웃: %s", e)
raise
except requests.exceptions.ConnectionError as e:
logger.warning("텔레그램 연결 오류: %s", e)
raise
except requests.exceptions.HTTPError as e:
logger.warning("텔레그램 HTTP 오류: %s", e)
raise
except requests.exceptions.RequestException as e:
logger.warning("텔레그램 API 요청 실패: %s", e)
raise
else:
# ✅ 메시지 분할 전송
chunks = [payload_text[i : i + max_length] for i in range(0, len(payload_text), max_length)]
logger.info("텔레그램 메시지 길이 초과 (%d자), %d개로 분할 전송", len(payload_text), len(chunks))
for i, chunk in enumerate(chunks, 1):
header = f"[메시지 {i}/{len(chunks)}]\n" if len(chunks) > 1 else ""
payload = {"chat_id": chat_id, "text": header + chunk}
if parse_mode:
payload["parse_mode"] = parse_mode
try:
resp = requests.post(url, json=payload, timeout=TELEGRAM_REQUEST_TIMEOUT)
resp.raise_for_status()
logger.debug("텔레그램 분할 메시지 전송 성공 (%d/%d)", i, len(chunks))
# Rate Limit 방지
if i < len(chunks):
time.sleep(TELEGRAM_RATE_LIMIT_DELAY)
except requests.exceptions.RequestException as e:
logger.error("텔레그램 분할 메시지 전송 실패 (%d/%d): %s", i, len(chunks), e)
raise
return True
except (requests.exceptions.Timeout, requests.exceptions.ConnectionError) as e:
# 네트워크 오류: 로깅하고 예외 발생
logger.warning("텔레그램 네트워크 오류 (타임아웃/연결): %s", e)
raise
except requests.exceptions.RequestException as e:
logger.warning("텔레그램 API 요청 실패: %s", e)
raise # 예외를 다시 발생시켜 호출자가 처리하도록 함
def report_error(bot_token: str, chat_id: str, message: str, dry_run: bool):