fix(signal_v2-phase3a): V1 KIS env pattern + test isolation fix
config.py: kis_env_type (virtual/real) + KIS_REAL_*/KIS_VIRTUAL_* env variables (V1 호환). kis_app_key/kis_app_secret/kis_account properties auto-select based on env type. main.py: KIS not-configured warning uses descriptive message including env type + expected var name. test_main.py: monkeypatch load_dotenv to no-op + setenv empty string (instead of delenv) — defeats .env re-read on importlib.reload. Pre-existing test_startup_warns_if_webai_api_key_missing also fixed. 33/33 tests pass (was 31/33). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -20,13 +20,14 @@ class Settings:
|
|||||||
db_path: Path = field(
|
db_path: Path = field(
|
||||||
default_factory=lambda: Path(__file__).parent / "data" / "signal_v2.db"
|
default_factory=lambda: Path(__file__).parent / "data" / "signal_v2.db"
|
||||||
)
|
)
|
||||||
# Phase 3a additions
|
# KIS — V1 호환 패턴 (KIS_ENV_TYPE virtual/real)
|
||||||
kis_app_key: str = field(default_factory=lambda: os.getenv("KIS_APP_KEY", "").strip())
|
kis_env_type: str = field(default_factory=lambda: os.getenv("KIS_ENV_TYPE", "virtual").lower())
|
||||||
kis_app_secret: str = field(default_factory=lambda: os.getenv("KIS_APP_SECRET", "").strip())
|
kis_real_app_key: str = field(default_factory=lambda: os.getenv("KIS_REAL_APP_KEY", "").strip())
|
||||||
kis_account: str = field(default_factory=lambda: os.getenv("KIS_ACCOUNT", "").strip())
|
kis_real_app_secret: str = field(default_factory=lambda: os.getenv("KIS_REAL_APP_SECRET", "").strip())
|
||||||
kis_is_virtual: bool = field(
|
kis_real_account: str = field(default_factory=lambda: os.getenv("KIS_REAL_ACCOUNT", "").strip())
|
||||||
default_factory=lambda: os.getenv("KIS_IS_VIRTUAL", "true").lower() == "true"
|
kis_virtual_app_key: str = field(default_factory=lambda: os.getenv("KIS_VIRTUAL_APP_KEY", "").strip())
|
||||||
)
|
kis_virtual_app_secret: str = field(default_factory=lambda: os.getenv("KIS_VIRTUAL_APP_SECRET", "").strip())
|
||||||
|
kis_virtual_account: str = field(default_factory=lambda: os.getenv("KIS_VIRTUAL_ACCOUNT", "").strip())
|
||||||
v1_token_path: Path = field(
|
v1_token_path: Path = field(
|
||||||
default_factory=lambda: Path(
|
default_factory=lambda: Path(
|
||||||
os.getenv("V1_TOKEN_PATH",
|
os.getenv("V1_TOKEN_PATH",
|
||||||
@@ -34,6 +35,22 @@ class Settings:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kis_is_virtual(self) -> bool:
|
||||||
|
return self.kis_env_type != "real"
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kis_app_key(self) -> str:
|
||||||
|
return self.kis_real_app_key if self.kis_env_type == "real" else self.kis_virtual_app_key
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kis_app_secret(self) -> str:
|
||||||
|
return self.kis_real_app_secret if self.kis_env_type == "real" else self.kis_virtual_app_secret
|
||||||
|
|
||||||
|
@property
|
||||||
|
def kis_account(self) -> str:
|
||||||
|
return self.kis_real_account if self.kis_env_type == "real" else self.kis_virtual_account
|
||||||
|
|
||||||
|
|
||||||
def get_settings() -> Settings:
|
def get_settings() -> Settings:
|
||||||
return Settings()
|
return Settings()
|
||||||
|
|||||||
@@ -38,7 +38,8 @@ async def lifespan(app: FastAPI):
|
|||||||
)
|
)
|
||||||
if not settings.kis_app_key:
|
if not settings.kis_app_key:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"KIS_APP_KEY not configured — KIS REST/WebSocket disabled"
|
"KIS app_key not configured (KIS_ENV_TYPE=%s, KIS_%s_APP_KEY missing) — KIS REST/WebSocket disabled",
|
||||||
|
settings.kis_env_type, settings.kis_env_type.upper()
|
||||||
)
|
)
|
||||||
|
|
||||||
_ctx.client = StockClient(settings.stock_api_url, settings.webai_api_key)
|
_ctx.client = StockClient(settings.stock_api_url, settings.webai_api_key)
|
||||||
|
|||||||
@@ -23,11 +23,15 @@ def test_health_endpoint_returns_status_online(monkeypatch):
|
|||||||
|
|
||||||
|
|
||||||
def test_startup_warns_if_webai_api_key_missing(monkeypatch, caplog):
|
def test_startup_warns_if_webai_api_key_missing(monkeypatch, caplog):
|
||||||
monkeypatch.delenv("WEBAI_API_KEY", raising=False)
|
# Use setenv with empty string + no-op load_dotenv to defeat .env re-read on reload
|
||||||
|
monkeypatch.setattr("signal_v2.config.load_dotenv", lambda *a, **k: None)
|
||||||
|
monkeypatch.setenv("WEBAI_API_KEY", "")
|
||||||
monkeypatch.setenv("STOCK_API_URL", "https://test.stock.local")
|
monkeypatch.setenv("STOCK_API_URL", "https://test.stock.local")
|
||||||
import importlib
|
import importlib
|
||||||
from signal_v2 import config as cfg
|
from signal_v2 import config as cfg
|
||||||
importlib.reload(cfg)
|
importlib.reload(cfg)
|
||||||
|
# After reload, load_dotenv reference is fresh — re-patch
|
||||||
|
monkeypatch.setattr("signal_v2.config.load_dotenv", lambda *a, **k: None)
|
||||||
from signal_v2 import main as main_mod
|
from signal_v2 import main as main_mod
|
||||||
importlib.reload(main_mod)
|
importlib.reload(main_mod)
|
||||||
with caplog.at_level(logging.WARNING, logger="signal_v2.main"):
|
with caplog.at_level(logging.WARNING, logger="signal_v2.main"):
|
||||||
@@ -37,17 +41,22 @@ def test_startup_warns_if_webai_api_key_missing(monkeypatch, caplog):
|
|||||||
|
|
||||||
|
|
||||||
def test_startup_warns_if_kis_app_key_missing(monkeypatch, caplog):
|
def test_startup_warns_if_kis_app_key_missing(monkeypatch, caplog):
|
||||||
"""KIS_APP_KEY 미설정 시 startup WARNING (KIS 호출 disabled)."""
|
"""KIS app_key 미설정 시 startup WARNING (KIS 호출 disabled) — V1 패턴."""
|
||||||
|
monkeypatch.setattr("signal_v2.config.load_dotenv", lambda *a, **k: None)
|
||||||
monkeypatch.setenv("STOCK_API_URL", "https://test.stock.local")
|
monkeypatch.setenv("STOCK_API_URL", "https://test.stock.local")
|
||||||
monkeypatch.setenv("WEBAI_API_KEY", "test-secret")
|
monkeypatch.setenv("WEBAI_API_KEY", "test-secret")
|
||||||
monkeypatch.delenv("KIS_APP_KEY", raising=False)
|
# V1 pattern: kis_env_type=virtual, both virtual keys empty
|
||||||
|
monkeypatch.setenv("KIS_ENV_TYPE", "virtual")
|
||||||
|
monkeypatch.setenv("KIS_VIRTUAL_APP_KEY", "")
|
||||||
|
monkeypatch.setenv("KIS_REAL_APP_KEY", "")
|
||||||
|
|
||||||
import importlib
|
import importlib
|
||||||
from signal_v2 import config as cfg
|
from signal_v2 import config as cfg
|
||||||
importlib.reload(cfg)
|
importlib.reload(cfg)
|
||||||
|
monkeypatch.setattr("signal_v2.config.load_dotenv", lambda *a, **k: None)
|
||||||
from signal_v2 import main as main_mod
|
from signal_v2 import main as main_mod
|
||||||
importlib.reload(main_mod)
|
importlib.reload(main_mod)
|
||||||
with caplog.at_level(logging.WARNING, logger="signal_v2.main"):
|
with caplog.at_level(logging.WARNING, logger="signal_v2.main"):
|
||||||
with TestClient(main_mod.app) as client:
|
with TestClient(main_mod.app) as client:
|
||||||
client.get("/health")
|
client.get("/health")
|
||||||
assert any("KIS_APP_KEY" in rec.message for rec in caplog.records)
|
assert any("KIS" in rec.message and "app_key" in rec.message.lower() for rec in caplog.records)
|
||||||
|
|||||||
Reference in New Issue
Block a user