"""미국 주식 거래소 감지 모듈 (exchange_detector.py) 테스트 테스트 시나리오: 1. is_us_stock() — 한국/미국 종목 코드 구분 2. detect_exchange() — 거래소 자동 판별 3. get_market_info() — 시장 정보 딕셔너리 반환 4. 엣지 케이스 — 빈 문자열, 특수문자, ETF 티커 """ import sys import os sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..")) import pytest from unittest.mock import patch, MagicMock from app.services.exchange_detector import ( is_us_stock, detect_exchange, get_market_info, KNOWN_EXCHANGES, ) # ─ is_us_stock 테스트 ────────────────────────────────────────────────────────── class TestIsUsStock: """미국 주식 여부 판별 테스트""" def test_korean_6digit_code_returns_false(self): """한국 6자리 숫자 코드는 False 반환""" assert is_us_stock("005930") is False # 삼성전자 assert is_us_stock("035720") is False # 카카오 assert is_us_stock("000660") is False # SK하이닉스 def test_us_ticker_returns_true(self): """미국 영문 티커는 True 반환""" assert is_us_stock("AAPL") is True assert is_us_stock("MSFT") is True assert is_us_stock("TSLA") is True def test_short_numeric_code_returns_true(self): """6자리가 아닌 숫자는 미국 주식으로 간주 (is_us_stock 로직상)""" assert is_us_stock("12345") is True # 5자리 숫자 assert is_us_stock("1234567") is True # 7자리 숫자 def test_empty_string_returns_false(self): """빈 문자열은 False 반환""" assert is_us_stock("") is False def test_mixed_alphanumeric(self): """영문+숫자 혼합은 미국 주식으로 간주""" assert is_us_stock("BRK-B") is True assert is_us_stock("A1234") is True def test_special_characters(self): """특수문자 포함 티커""" assert is_us_stock("BRK.B") is True assert is_us_stock("@#$") is True # 숫자 6자리 아니므로 True def test_etf_tickers(self): """ETF 티커도 미국 주식으로 판별""" assert is_us_stock("SPY") is True assert is_us_stock("QQQ") is True assert is_us_stock("ARKK") is True assert is_us_stock("SOXL") is True # ─ detect_exchange 테스트 ────────────────────────────────────────────────────── class TestDetectExchange: """거래소 감지 테스트""" def test_known_nasdaq_tickers(self): """NASDAQ 종목 정상 반환""" assert detect_exchange("AAPL") == "NASD" assert detect_exchange("MSFT") == "NASD" assert detect_exchange("NVDA") == "NASD" assert detect_exchange("TSLA") == "NASD" def test_known_nyse_tickers(self): """NYSE 종목 정상 반환""" assert detect_exchange("JPM") == "NYSE" assert detect_exchange("V") == "NYSE" assert detect_exchange("WMT") == "NYSE" assert detect_exchange("DIS") == "NYSE" def test_known_amex_tickers(self): """AMEX 종목 정상 반환""" assert detect_exchange("SPY") == "AMEX" assert detect_exchange("QQQ") == "AMEX" assert detect_exchange("GLD") == "AMEX" assert detect_exchange("ARKK") == "AMEX" def test_unknown_ticker_defaults_to_nasd(self): """미등록 티커는 기본값 NASD 반환""" assert detect_exchange("XYZABC") == "NASD" assert detect_exchange("UNKNOWN") == "NASD" def test_case_insensitive(self): """대소문자 구분 없이 동작""" assert detect_exchange("aapl") == "NASD" assert detect_exchange("Aapl") == "NASD" assert detect_exchange("jpm") == "NYSE" def test_whitespace_stripped(self): """앞뒤 공백 제거 후 조회""" assert detect_exchange(" AAPL ") == "NASD" assert detect_exchange(" JPM ") == "NYSE" def test_leveraged_etf_tickers(self): """레버리지/인버스 ETF 거래소 조회""" assert detect_exchange("SQQQ") == "NASD" assert detect_exchange("TQQQ") == "NASD" assert detect_exchange("SOXL") == "AMEX" assert detect_exchange("SOXS") == "AMEX" # ─ get_market_info 테스트 ────────────────────────────────────────────────────── class TestGetMarketInfo: """시장 정보 반환 테스트""" def test_us_stock_returns_us_market(self): """미국 주식은 US 시장 정보 반환""" info = get_market_info("AAPL") assert info["market"] == "US" assert info["exchange"] == "NASD" assert info["currency"] == "USD" def test_kr_stock_returns_kr_market(self): """한국 주식은 KR 시장 정보 반환""" info = get_market_info("005930") assert info["market"] == "KR" assert info["exchange"] == "KRX" assert info["currency"] == "KRW" def test_nyse_stock_info(self): """NYSE 종목 시장 정보""" info = get_market_info("JPM") assert info["market"] == "US" assert info["exchange"] == "NYSE" assert info["currency"] == "USD" def test_amex_etf_info(self): """AMEX ETF 시장 정보""" info = get_market_info("SPY") assert info["market"] == "US" assert info["exchange"] == "AMEX" assert info["currency"] == "USD" def test_empty_string_returns_kr_default(self): """빈 문자열은 KR 기본값 반환""" info = get_market_info("") assert info["market"] == "KR" assert info["exchange"] == "KRX" assert info["currency"] == "KRW" def test_returns_all_required_keys(self): """반환 딕셔너리에 필수 키가 모두 존재""" required_keys = {"market", "exchange", "currency"} assert required_keys <= set(get_market_info("AAPL").keys()) assert required_keys <= set(get_market_info("005930").keys()) assert required_keys <= set(get_market_info("").keys()) # ─ KNOWN_EXCHANGES 매핑 검증 ────────────────────────────────────────────────── class TestKnownExchangesMapping: """하드코딩 매핑 데이터 일관성 테스트""" def test_all_values_are_valid_exchanges(self): """모든 거래소 코드가 유효한 값""" valid_exchanges = {"NASD", "NYSE", "AMEX"} for ticker, exchange in KNOWN_EXCHANGES.items(): assert exchange in valid_exchanges, f"{ticker} has invalid exchange: {exchange}" def test_all_keys_are_uppercase(self): """모든 티커 키가 대문자""" for ticker in KNOWN_EXCHANGES: assert ticker == ticker.upper(), f"Ticker not uppercase: {ticker}" def test_mapping_has_sufficient_entries(self): """충분한 수의 종목이 매핑되어 있음""" assert len(KNOWN_EXCHANGES) >= 100, ( f"매핑 종목 수 부족: {len(KNOWN_EXCHANGES)}개" ) if __name__ == "__main__": pytest.main([__file__, "-v"])