Refactor unit tests to remove pytest dependency and use unittest.TestCase
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from unittest.mock import patch
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
if str(ROOT) not in sys.path:
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
import pytest
|
||||
|
||||
from src.quant_engine.kis_api_client_v1 import (
|
||||
KisCredentials,
|
||||
OrderEndpointBlockedError,
|
||||
@@ -30,69 +30,72 @@ FORBIDDEN_ORDER_TR_IDS = (
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", FORBIDDEN_ORDER_PATHS)
|
||||
def test_order_path_is_blocked(path: str):
|
||||
with pytest.raises(OrderEndpointBlockedError):
|
||||
_assert_read_only(path, "FHKST01010100")
|
||||
class TestKisApiClientV1(unittest.TestCase):
|
||||
|
||||
def test_order_path_is_blocked(self):
|
||||
for path in FORBIDDEN_ORDER_PATHS:
|
||||
with self.assertRaises(OrderEndpointBlockedError):
|
||||
_assert_read_only(path, "FHKST01010100")
|
||||
|
||||
def test_order_tr_id_is_blocked(self):
|
||||
for tr_id in FORBIDDEN_ORDER_TR_IDS:
|
||||
with self.assertRaises(OrderEndpointBlockedError):
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/inquire-price", tr_id)
|
||||
|
||||
def test_known_readonly_endpoints_pass(self):
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/inquire-price", "FHKST01010100")
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn", "FHKST01010200")
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/daily-short-sale", "FHPST04830000")
|
||||
|
||||
def test_no_order_endpoint_substring_anywhere_in_kis_client_source(self):
|
||||
"""정적 검증 — 누군가 향후 주문 함수를 추가하더라도 경로 문자열이 소스에 남으면 즉시 탐지.
|
||||
|
||||
TTTC8434R/VTTC8434R(주식잔고조회)는 FORBIDDEN_TR_ID_PREFIXES 차단목록 '데이터'로
|
||||
이 파일에 의도적으로 존재한다(prefix가 아닌 전체 TR_ID라 prefix-매칭으로는 막을 수
|
||||
없어 명시적으로 등재) — 이 두 개는 검사에서 제외한다. 전체 코드베이스 차원의
|
||||
"차단목록 외 파일에는 한 글자도 없어야 한다"는 보장은
|
||||
tools/validate_no_direct_api_trading_v1.py(ALLOWLISTED_FILES 제외 전체 스캔)가 맡는다.
|
||||
"""
|
||||
source = (ROOT / "src" / "quant_engine" / "kis_api_client_v1.py").read_text(encoding="utf-8")
|
||||
blocklist_data_exceptions = {"TTTC8434R", "VTTC8434R"}
|
||||
for forbidden_path in FORBIDDEN_ORDER_PATHS:
|
||||
self.assertNotIn(forbidden_path, source, f"주문 엔드포인트 경로가 소스에 존재함: {forbidden_path}")
|
||||
for forbidden_tr_id in FORBIDDEN_ORDER_TR_IDS:
|
||||
if forbidden_tr_id in blocklist_data_exceptions:
|
||||
continue
|
||||
self.assertNotIn(forbidden_tr_id, source, f"주문 TR_ID가 소스에 존재함: {forbidden_tr_id}")
|
||||
|
||||
def test_kis_client_module_defines_no_order_submission_function(self):
|
||||
import src.quant_engine.kis_api_client_v1 as kis_module
|
||||
|
||||
public_names = [name for name in dir(kis_module) if not name.startswith("_")]
|
||||
banned_keywords = (
|
||||
"place_order", "submit_order", "cancel_order", "revise_order", "send_order",
|
||||
"inquire_balance", "account_balance",
|
||||
)
|
||||
for name in public_names:
|
||||
lowered = name.lower()
|
||||
for banned in banned_keywords:
|
||||
self.assertNotIn(banned, lowered, f"주문 제출/정정/취소로 의심되는 함수가 존재함: {name}")
|
||||
|
||||
def test_kis_credentials_load_uses_required_env_vars(self):
|
||||
with patch.dict("os.environ", {
|
||||
"KIS_APP_Key": "real-key",
|
||||
"KIS_APP_Secret": "real-secret",
|
||||
"KIS_APP_Key_TEST": "mock-key",
|
||||
"KIS_APP_Secret_TEST": "mock-secret"
|
||||
}):
|
||||
real = KisCredentials.load("real")
|
||||
mock = KisCredentials.load("mock")
|
||||
|
||||
self.assertEqual(real.app_key, "real-key")
|
||||
self.assertEqual(real.app_secret, "real-secret")
|
||||
self.assertEqual(real.account, "real")
|
||||
self.assertEqual(mock.app_key, "mock-key")
|
||||
self.assertEqual(mock.app_secret, "mock-secret")
|
||||
self.assertEqual(mock.account, "mock")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("tr_id", FORBIDDEN_ORDER_TR_IDS)
|
||||
def test_order_tr_id_is_blocked(tr_id: str):
|
||||
with pytest.raises(OrderEndpointBlockedError):
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/inquire-price", tr_id)
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
|
||||
def test_known_readonly_endpoints_pass():
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/inquire-price", "FHKST01010100")
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/inquire-asking-price-exp-ccn", "FHKST01010200")
|
||||
_assert_read_only("/uapi/domestic-stock/v1/quotations/daily-short-sale", "FHPST04830000")
|
||||
|
||||
|
||||
def test_no_order_endpoint_substring_anywhere_in_kis_client_source():
|
||||
"""정적 검증 — 누군가 향후 주문 함수를 추가하더라도 경로 문자열이 소스에 남으면 즉시 탐지.
|
||||
|
||||
TTTC8434R/VTTC8434R(주식잔고조회)는 FORBIDDEN_TR_ID_PREFIXES 차단목록 '데이터'로
|
||||
이 파일에 의도적으로 존재한다(prefix가 아닌 전체 TR_ID라 prefix-매칭으로는 막을 수
|
||||
없어 명시적으로 등재) — 이 두 개는 검사에서 제외한다. 전체 코드베이스 차원의
|
||||
"차단목록 외 파일에는 한 글자도 없어야 한다"는 보장은
|
||||
tools/validate_no_direct_api_trading_v1.py(ALLOWLISTED_FILES 제외 전체 스캔)가 맡는다.
|
||||
"""
|
||||
source = (ROOT / "src" / "quant_engine" / "kis_api_client_v1.py").read_text(encoding="utf-8")
|
||||
blocklist_data_exceptions = {"TTTC8434R", "VTTC8434R"}
|
||||
for forbidden_path in FORBIDDEN_ORDER_PATHS:
|
||||
assert forbidden_path not in source, f"주문 엔드포인트 경로가 소스에 존재함: {forbidden_path}"
|
||||
for forbidden_tr_id in FORBIDDEN_ORDER_TR_IDS:
|
||||
if forbidden_tr_id in blocklist_data_exceptions:
|
||||
continue
|
||||
assert forbidden_tr_id not in source, f"주문 TR_ID가 소스에 존재함: {forbidden_tr_id}"
|
||||
|
||||
|
||||
def test_kis_client_module_defines_no_order_submission_function():
|
||||
import src.quant_engine.kis_api_client_v1 as kis_module
|
||||
|
||||
public_names = [name for name in dir(kis_module) if not name.startswith("_")]
|
||||
banned_keywords = (
|
||||
"place_order", "submit_order", "cancel_order", "revise_order", "send_order",
|
||||
"inquire_balance", "account_balance",
|
||||
)
|
||||
for name in public_names:
|
||||
lowered = name.lower()
|
||||
for banned in banned_keywords:
|
||||
assert banned not in lowered, f"주문 제출/정정/취소로 의심되는 함수가 존재함: {name}"
|
||||
|
||||
|
||||
def test_kis_credentials_load_uses_required_env_vars(monkeypatch):
|
||||
monkeypatch.setenv("KIS_APP_Key", "real-key")
|
||||
monkeypatch.setenv("KIS_APP_Secret", "real-secret")
|
||||
monkeypatch.setenv("KIS_APP_Key_TEST", "mock-key")
|
||||
monkeypatch.setenv("KIS_APP_Secret_TEST", "mock-secret")
|
||||
|
||||
real = KisCredentials.load("real")
|
||||
mock = KisCredentials.load("mock")
|
||||
|
||||
assert real.app_key == "real-key"
|
||||
assert real.app_secret == "real-secret"
|
||||
assert real.account == "real"
|
||||
assert mock.app_key == "mock-key"
|
||||
assert mock.app_secret == "mock-secret"
|
||||
assert mock.account == "mock"
|
||||
|
||||
Reference in New Issue
Block a user