"""BAT-020: REQ-046 BrokerCredential daemon_id 필드 단위 테스트.""" from __future__ import annotations from datetime import datetime, timezone import pytest from app.domain.models import BrokerCredential # --------------------------------------------------------------------------- # REQ-046 AC-1: daemon_id=None 기본값 # --------------------------------------------------------------------------- def test_broker_credential_daemon_id_defaults_to_none() -> None: """REQ-046 AC-1: daemon_id 미지정 시 None이 기본값.""" cred = BrokerCredential( user_id="user-1", broker="kis", account_ref="12345678", encrypted_payload="encrypted", ) assert hasattr(cred, "daemon_id") assert cred.daemon_id is None # --------------------------------------------------------------------------- # REQ-046 AC-2: daemon_id 값 지정 # --------------------------------------------------------------------------- def test_broker_credential_daemon_id_set() -> None: """REQ-046 AC-2: daemon_id="daemon-abc" 지정 시 해당 값으로 세팅.""" cred = BrokerCredential( user_id="user-1", broker="kis", account_ref="12345678", encrypted_payload="encrypted", daemon_id="daemon-abc", ) assert cred.daemon_id == "daemon-abc" # --------------------------------------------------------------------------- # REQ-046 AC-3: _row_to_broker_credential 7컬럼(daemon_id 포함) 처리 # --------------------------------------------------------------------------- def test_row_to_broker_credential_with_daemon_id() -> None: """REQ-046 AC-3: 7컬럼 행(daemon_id 포함) 변환 시 daemon_id 세팅.""" from app.repositories.postgres import PostgresPortfolioRepository now = datetime.now(timezone.utc) row = ("user-1", "kis", "12345678", "encrypted", 2, now, "daemon-xyz") cred = PostgresPortfolioRepository._row_to_broker_credential(row) assert cred.user_id == "user-1" assert cred.broker == "kis" assert cred.account_ref == "12345678" assert cred.encrypted_payload == "encrypted" assert cred.credential_version == 2 assert cred.last_rotated_at == now assert cred.daemon_id == "daemon-xyz" # --------------------------------------------------------------------------- # REQ-046 AC-4: _row_to_broker_credential 6컬럼(daemon_id 없음) 하위호환 # --------------------------------------------------------------------------- def test_row_to_broker_credential_without_daemon_id() -> None: """REQ-046 AC-4: 6컬럼 행(daemon_id 없음) 변환 시 daemon_id=None (하위호환).""" from app.repositories.postgres import PostgresPortfolioRepository now = datetime.now(timezone.utc) row = ("user-1", "kis", "12345678", "encrypted", 1, now) cred = PostgresPortfolioRepository._row_to_broker_credential(row) assert cred.daemon_id is None # --------------------------------------------------------------------------- # REQ-046 AC-5/6: list_broker_credentials daemon_id 필터 (mock 기반) # --------------------------------------------------------------------------- class _FakeCursor: """psycopg Cursor 최소 mock.""" def __init__(self, rows): self._rows = rows self.last_query = None self.last_params = None def execute(self, query, params=None): self.last_query = query self.last_params = params def fetchall(self): return self._rows def __enter__(self): return self def __exit__(self, *args): pass class _FakeConn: def __init__(self, rows): self._rows = rows def cursor(self): return _FakeCursor(self._rows) def _make_rows_for_user(user_id: str): """daemon_id "d1", "d2" 각각 1건씩.""" now = datetime.now(timezone.utc) return [ (user_id, "kis", "ref-d1", "enc-d1", 1, now, "d1"), (user_id, "kis2", "ref-d2", "enc-d2", 1, now, "d2"), ] def test_list_broker_credentials_daemon_id_filter() -> None: """REQ-046 AC-5: daemon_id 필터 시 해당 daemon_id 행만 반환.""" from unittest.mock import MagicMock, patch from app.repositories.postgres import PostgresPortfolioRepository, PortfolioDBConfig rows = _make_rows_for_user("user-1") # daemon_id="d1" 필터 시 d1 행만 반환되는 mock d1_rows = [rows[0]] config = PortfolioDBConfig( host="localhost", port=5432, dbname="test", user="test", password="test", sslmode="disable", ) repo = PostgresPortfolioRepository(config) mock_cursor = MagicMock() mock_cursor.__enter__ = lambda s: mock_cursor mock_cursor.__exit__ = MagicMock(return_value=False) mock_cursor.fetchall.return_value = d1_rows mock_conn = MagicMock() mock_conn.cursor.return_value = mock_cursor repo._connection = mock_conn # ensure_schema 무력화 with patch.object(repo, "ensure_schema"): results = repo.list_broker_credentials("user-1", daemon_id="d1") assert len(results) == 1 assert results[0].daemon_id == "d1" assert results[0].broker == "kis" def test_list_broker_credentials_no_daemon_id_filter_returns_all() -> None: """REQ-046 AC-6: daemon_id 미지정 시 전체 반환 (하위호환).""" from unittest.mock import MagicMock, patch from app.repositories.postgres import PostgresPortfolioRepository, PortfolioDBConfig rows = _make_rows_for_user("user-1") config = PortfolioDBConfig( host="localhost", port=5432, dbname="test", user="test", password="test", sslmode="disable", ) repo = PostgresPortfolioRepository(config) mock_cursor = MagicMock() mock_cursor.__enter__ = lambda s: mock_cursor mock_cursor.__exit__ = MagicMock(return_value=False) mock_cursor.fetchall.return_value = rows mock_conn = MagicMock() mock_conn.cursor.return_value = mock_cursor repo._connection = mock_conn with patch.object(repo, "ensure_schema"): results = repo.list_broker_credentials("user-1") assert len(results) == 2