Files
auto-stock/backend/tests/test_timezone_consistency.py
shuim ec2fa2b423 fix: DB 타임스탬프 KST 통일 + KIS 토큰 캐시 영속화
시간대 혼재: 모델 기본값은 datetime.utcnow(UTC), 수동 매매 등 일부 경로는 _get_kst_now(KST)로
저장해 같은 테이블에 9시간 차가 섞였다(화면 매매시각 02:14 등). 조회 구간(utcnow - days)도
KST 저장분과 비교돼 어긋났고, 추가매수 쿨다운은 (UTC now - KST created_at)이 음수라 상시
발동했다. app.core.timeutil.now_kst() 도입 후 모델 기본값 27곳과 app/ 전역 utcnow() 호출을
모두 KST 기준으로 통일(잔존 0건, 테스트로 고정).

KIS 토큰 캐시(~/.cache/auto-stock)가 컨테이너에만 있어 배포마다 사라지고 토큰을 새로
발급받다 KIS가 EGW00103(유효하지 않은 AppKey)로 거부 → 502·잔고 0 유발. 볼륨 마운트로
재배포 후에도 재사용.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 11:24:14 +09:00

62 lines
2.2 KiB
Python

"""DB 타임스탬프 KST 통일 검증.
2026-09-23: 모델 기본값은 datetime.utcnow(UTC), 일부 경로는 _get_kst_now(KST)로 저장해
같은 테이블 안에서 9시간이 섞였다(화면 매매 시각이 02:14 처럼 표시). 조회 구간도
utcnow 기준이라 비교가 9시간 어긋났다(추가매수 쿨다운 상시 발동).
"""
import inspect
import pathlib
from datetime import datetime, timedelta
import pytz
from app.core.timeutil import KST, now_kst, today_kst
from app.models.models import ActiveTrade, Portfolio, TradeHistory
def test_now_kst_is_naive_korean_time():
v = now_kst()
assert v.tzinfo is None
real = datetime.now(pytz.timezone("Asia/Seoul")).replace(tzinfo=None)
assert abs((real - v).total_seconds()) < 5
assert today_kst() == real.date()
def test_now_kst_differs_from_utc_by_nine_hours():
delta = now_kst() - datetime.utcnow()
assert 8.9 * 3600 < delta.total_seconds() < 9.1 * 3600
def test_model_defaults_use_kst():
for model, cols in [
(TradeHistory, ["created_at"]),
(ActiveTrade, ["created_at", "updated_at"]),
(Portfolio, ["updated_at"]),
]:
for col in cols:
default = model.__table__.columns[col].default
assert default is not None, f"{model.__name__}.{col} 기본값 없음"
assert getattr(default.arg, "__name__", "") == "now_kst", (
f"{model.__name__}.{col} 이 KST 기준이 아님"
)
def test_no_utcnow_left_in_app_code():
"""app/ 전체에서 utcnow() 호출이 남아 있으면 다시 섞인다."""
root = pathlib.Path(__file__).resolve().parents[1] / "app"
offenders = []
for f in root.rglob("*.py"):
for i, line in enumerate(f.read_text().splitlines(), 1):
code = line.split("#", 1)[0]
if "utcnow()" in code:
offenders.append(f"{f.relative_to(root)}:{i}")
assert not offenders, f"utcnow() 잔존: {offenders}"
def test_cooldown_compares_same_basis():
from app.services import trade_executor as te
src = inspect.getsource(te)
i = src.index("recent_buys[0].created_at")
window = src[max(0, i - 300): i + 100]
assert "now_kst()" in window, "쿨다운 비교가 KST 기준이 아님"