Files
web-page-backend/stock-lab/app/test_price_fetcher.py
gahusb a826e00399 feat(stock): NXT 시간외 거래가를 정규장 마감 후 자동 연결
네이버 모바일 주식 API의 overMarketPriceInfo를 인식해 NXT 프리/애프터마켓
운영 중이면 overPrice를 current_price로 자동 전환. 포트폴리오 응답에
price_session(REGULAR/NXT_PRE/NXT_AFTER/CLOSED)과 price_as_of 메타 동봉.

이전엔 closePrice만 사용해 15:30 이후 NXT 거래가 진행 중이어도 평가금액이
동결됐음. 이제 가격이 자연스럽게 이어짐. _select_price_from_response는
순수 함수로 분리, unittest 8케이스로 회귀 방지.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 19:32:10 +09:00

132 lines
5.1 KiB
Python

"""price_fetcher._select_price_from_response 단위 테스트.
실행:
cd web-backend/stock-lab
python -m unittest app.test_price_fetcher -v
"""
import os
import sys
import unittest
# app 패키지를 직접 실행 가능하도록
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.price_fetcher import _select_price_from_response
class SelectPriceFromResponseTest(unittest.TestCase):
def test_regular_session_uses_close_price(self):
"""정규장 운영 중이면 closePrice를 REGULAR 세션으로 반환."""
payload = {
"closePrice": "70,500",
"marketStatus": "OPEN",
"localTradedAt": "2026-05-11T11:23:45+09:00",
"overMarketPriceInfo": None,
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 70500)
self.assertEqual(result["session"], "REGULAR")
self.assertEqual(result["as_of"], "2026-05-11T11:23:45+09:00")
def test_nxt_after_market_open_uses_over_price(self):
"""정규장 마감 + NXT 애프터마켓 운영중이면 overPrice를 NXT_AFTER 세션으로 반환."""
payload = {
"closePrice": "285,500",
"marketStatus": "CLOSE",
"localTradedAt": "2026-05-11T15:30:00+09:00",
"overMarketPriceInfo": {
"tradingSessionType": "AFTER_MARKET",
"overMarketStatus": "OPEN",
"overPrice": "285,000",
"localTradedAt": "2026-05-11T19:21:40+09:00",
"tradeStopType": {"name": "TRADING"},
},
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 285000)
self.assertEqual(result["session"], "NXT_AFTER")
self.assertEqual(result["as_of"], "2026-05-11T19:21:40+09:00")
def test_nxt_pre_market_open_uses_over_price(self):
"""NXT 프리마켓 운영중이면 NXT_PRE 세션 + overPrice."""
payload = {
"closePrice": "70,500",
"marketStatus": "CLOSE",
"localTradedAt": "2026-05-10T15:30:00+09:00",
"overMarketPriceInfo": {
"tradingSessionType": "PRE_MARKET",
"overMarketStatus": "OPEN",
"overPrice": "70,800",
"localTradedAt": "2026-05-11T08:30:00+09:00",
"tradeStopType": {"name": "TRADING"},
},
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 70800)
self.assertEqual(result["session"], "NXT_PRE")
self.assertEqual(result["as_of"], "2026-05-11T08:30:00+09:00")
def test_nxt_closed_falls_back_to_close_price(self):
"""NXT가 CLOSE 상태이면 closePrice 사용, 세션은 CLOSED."""
payload = {
"closePrice": "285,500",
"marketStatus": "CLOSE",
"localTradedAt": "2026-05-11T15:30:00+09:00",
"overMarketPriceInfo": {
"tradingSessionType": "AFTER_MARKET",
"overMarketStatus": "CLOSE",
"overPrice": "285,000",
"tradeStopType": {"name": "TRADING"},
},
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 285500)
self.assertEqual(result["session"], "CLOSED")
def test_nxt_trading_halted_falls_back_to_close_price(self):
"""NXT OPEN이지만 tradeStopType이 TRADING이 아니면 closePrice 사용."""
payload = {
"closePrice": "285,500",
"marketStatus": "CLOSE",
"overMarketPriceInfo": {
"tradingSessionType": "AFTER_MARKET",
"overMarketStatus": "OPEN",
"overPrice": "285,000",
"tradeStopType": {"name": "STOP"},
},
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 285500)
self.assertEqual(result["session"], "CLOSED")
def test_no_over_market_info_returns_close_price(self):
"""overMarketPriceInfo 자체가 없는 경우(해외 종목 등) closePrice 그대로."""
payload = {
"closePrice": "150,000",
"marketStatus": "CLOSE",
"localTradedAt": "2026-05-11T15:30:00+09:00",
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 150000)
self.assertEqual(result["session"], "CLOSED")
def test_missing_close_price_returns_none(self):
"""closePrice가 없거나 비숫자면 price는 None."""
payload = {"closePrice": "", "marketStatus": "CLOSE"}
result = _select_price_from_response(payload)
self.assertIsNone(result["price"])
def test_alternate_stock_end_price_field(self):
"""일부 응답은 stockEndPrice 필드를 사용 — 폴백 인식."""
payload = {
"stockEndPrice": "12,345",
"marketStatus": "OPEN",
}
result = _select_price_from_response(payload)
self.assertEqual(result["price"], 12345)
self.assertEqual(result["session"], "REGULAR")
if __name__ == "__main__":
unittest.main()