refactor: rename stock-lab → stock (graduation)
- git mv stock-lab/ → stock/ - docker-compose.yml: 서비스 키 + container_name + build.context + frontend.depends_on + agent-office STOCK_LAB_URL → STOCK_URL - agent-office/app: config.py, service_proxy.py, agents/stock.py, tests/ STOCK_LAB_URL → STOCK_URL - nginx/default.conf: proxy_pass http://stock-lab → http://stock (3 lines) - CLAUDE.md / README.md / STATUS.md / scripts/ 문구 갱신 - stock/ 내부 자기 참조 갱신 lab 네이밍 정책 (feedback_lab_naming.md) graduation. API URL / Python import / DB 파일명 변경 없음.
This commit is contained in:
131
stock/app/test_price_fetcher.py
Normal file
131
stock/app/test_price_fetcher.py
Normal file
@@ -0,0 +1,131 @@
|
||||
"""price_fetcher._select_price_from_response 단위 테스트.
|
||||
|
||||
실행:
|
||||
cd web-backend/stock
|
||||
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()
|
||||
Reference in New Issue
Block a user