refactor: simplify data model by removing unused fields and constraints
- Remove runtime_id from AuthToken model - Remove market field from Portfolio model - Remove BrokerCredential and LedgerTrade unused models - Remove unique constraint on (owner_user_id, name) - Simplify postgres repository schema and queries - Clean up auth_service implementation - Update test suite for simplified model
This commit is contained in:
+19
-490
@@ -10,7 +10,7 @@ from typer.testing import CliRunner
|
||||
from app.cli.main import create_app
|
||||
from app.core.config import Settings
|
||||
from app.core.security import hash_token, token_prefix
|
||||
from app.domain.models import AuthToken, BrokerCredential, LedgerEntry, Portfolio, PortfolioComment, PortfolioTag, StrategySet, User
|
||||
from app.domain.models import AuthToken, LedgerEntry, Portfolio, PortfolioComment, PortfolioTag, StrategySet, User
|
||||
|
||||
|
||||
class FakeRepo:
|
||||
@@ -25,7 +25,6 @@ class FakeRepo:
|
||||
self._tags: dict[str, PortfolioTag] = {}
|
||||
self._auth_tokens: dict[str, AuthToken] = {}
|
||||
self._token_seq = 1
|
||||
self._broker_credentials: dict[tuple[str, str], BrokerCredential] = {}
|
||||
self.closed = False
|
||||
|
||||
def close(self) -> None:
|
||||
@@ -123,7 +122,6 @@ class FakeRepo:
|
||||
permission: str,
|
||||
token_type: str,
|
||||
expires_at: datetime | None = None,
|
||||
runtime_id: str | None = None,
|
||||
) -> AuthToken:
|
||||
token = AuthToken(
|
||||
id=f"token-{self._token_seq}",
|
||||
@@ -134,7 +132,6 @@ class FakeRepo:
|
||||
token_type=token_type,
|
||||
expires_at=expires_at,
|
||||
created_at=datetime.now(timezone.utc),
|
||||
runtime_id=runtime_id,
|
||||
)
|
||||
self._token_seq += 1
|
||||
self._auth_tokens[token.id] = token
|
||||
@@ -146,20 +143,6 @@ class FakeRepo:
|
||||
raise ValueError("토큰을 찾을 수 없습니다")
|
||||
token.revoked_at = datetime.now(timezone.utc)
|
||||
|
||||
def rotate_service_token(
|
||||
self,
|
||||
token_id: str,
|
||||
token_hash: str,
|
||||
token_prefix: str,
|
||||
runtime_id: str,
|
||||
) -> AuthToken:
|
||||
token = self._auth_tokens.get(token_id)
|
||||
if token is None or token.revoked_at is not None:
|
||||
raise ValueError("토큰을 찾을 수 없습니다")
|
||||
token.token_prefix = token_prefix
|
||||
token.runtime_id = runtime_id
|
||||
return token
|
||||
|
||||
def list_service_tokens(
|
||||
self,
|
||||
user_id: str | None = None,
|
||||
@@ -208,11 +191,12 @@ class FakeRepo:
|
||||
|
||||
def create_portfolio(
|
||||
self,
|
||||
name: str,
|
||||
portfolio_id: str,
|
||||
*,
|
||||
owner_user_id: str,
|
||||
broker: str,
|
||||
status: str,
|
||||
name: str,
|
||||
symbol: str,
|
||||
cash_balance: float,
|
||||
asset_quantity: float,
|
||||
@@ -221,7 +205,6 @@ class FakeRepo:
|
||||
active_strategy_id: str | None,
|
||||
avabile_strategies: list[str] | None,
|
||||
) -> Portfolio:
|
||||
portfolio_id = str(uuid.uuid4())
|
||||
self._portfolio = Portfolio(
|
||||
portfolio_id=portfolio_id,
|
||||
broker=broker,
|
||||
@@ -238,13 +221,6 @@ class FakeRepo:
|
||||
)
|
||||
return self._portfolio
|
||||
|
||||
def get_portfolio_by_name_and_user(self, owner_user_id: str, name: str) -> Portfolio | None:
|
||||
if self._portfolio is None:
|
||||
return None
|
||||
if self._portfolio.owner_user_id == owner_user_id and self._portfolio.name == name:
|
||||
return self._portfolio
|
||||
return None
|
||||
|
||||
def update_portfolio(self, portfolio_id: str, **fields: object) -> Portfolio:
|
||||
portfolio = self.get(portfolio_id)
|
||||
for key, value in fields.items():
|
||||
@@ -437,43 +413,6 @@ class FakeRepo:
|
||||
raise ValueError("댓글을 찾을 수 없습니다")
|
||||
del self._comments[comment_id]
|
||||
|
||||
# --- broker credentials ---
|
||||
|
||||
def upsert_broker_credential(
|
||||
self,
|
||||
user_id: str,
|
||||
broker: str,
|
||||
*,
|
||||
account_ref: str,
|
||||
encrypted_payload: str,
|
||||
) -> BrokerCredential:
|
||||
key = (user_id, broker)
|
||||
existing = self._broker_credentials.get(key)
|
||||
version = (existing.credential_version + 1) if existing else 1
|
||||
credential = BrokerCredential(
|
||||
user_id=user_id,
|
||||
broker=broker,
|
||||
account_ref=account_ref,
|
||||
encrypted_payload=encrypted_payload,
|
||||
credential_version=version,
|
||||
last_rotated_at=datetime.now(timezone.utc),
|
||||
)
|
||||
self._broker_credentials[key] = credential
|
||||
return credential
|
||||
|
||||
def list_broker_credentials(self, user_id: str) -> list[BrokerCredential]:
|
||||
return [c for (uid, _), c in self._broker_credentials.items() if uid == user_id]
|
||||
|
||||
def get_broker_credential(self, user_id: str, broker: str) -> BrokerCredential | None:
|
||||
return self._broker_credentials.get((user_id, broker))
|
||||
|
||||
def delete_broker_credential(self, user_id: str, broker: str) -> bool:
|
||||
key = (user_id, broker)
|
||||
if key in self._broker_credentials:
|
||||
del self._broker_credentials[key]
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _settings() -> Settings:
|
||||
return Settings(db_enabled=True, allowed_origins_raw="http://localhost:3000")
|
||||
@@ -491,7 +430,7 @@ def _portfolio() -> Portfolio:
|
||||
portfolio_id="005930:tv",
|
||||
broker="kis",
|
||||
status="enabled",
|
||||
name="005930:tv",
|
||||
name="Samsung Electronics",
|
||||
symbol="005930",
|
||||
cash_balance=1_000_000.0,
|
||||
asset_quantity=1.0,
|
||||
@@ -503,29 +442,21 @@ def _portfolio() -> Portfolio:
|
||||
|
||||
|
||||
def test_cli_list_renders_table() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", permission="read")
|
||||
p = _portfolio()
|
||||
p.owner_user_id = user.id
|
||||
repo._portfolio = p
|
||||
repo = FakeRepo(_portfolio())
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["portfolio", "list", "--user", "alice"])
|
||||
result = runner.invoke(runner.app, ["portfolio", "list"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "005930:tv" in result.stdout
|
||||
assert "id: 005930:tv" in result.stdout
|
||||
assert "Portfolios" in result.stdout
|
||||
|
||||
|
||||
def test_cli_status_supports_json_output() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", permission="read")
|
||||
p = _portfolio()
|
||||
p.owner_user_id = user.id
|
||||
repo._portfolio = p
|
||||
repo = FakeRepo(_portfolio())
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["--format", "json", "portfolio", "status", "005930:tv", "--user", "alice"])
|
||||
result = runner.invoke(runner.app, ["--format", "json", "portfolio", "status", "005930:tv"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
payload = json.loads(result.stdout)
|
||||
@@ -534,7 +465,6 @@ def test_cli_status_supports_json_output() -> None:
|
||||
|
||||
def test_cli_create_buy_and_delete_flow() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", permission="read")
|
||||
runner = build_runner(repo)
|
||||
|
||||
create_result = runner.invoke(
|
||||
@@ -545,8 +475,6 @@ def test_cli_create_buy_and_delete_flow() -> None:
|
||||
"005930:tv",
|
||||
"--symbol",
|
||||
"005930",
|
||||
"--user",
|
||||
"alice",
|
||||
"--cash-balance",
|
||||
"1000000",
|
||||
"--strategy-params",
|
||||
@@ -560,8 +488,6 @@ def test_cli_create_buy_and_delete_flow() -> None:
|
||||
"portfolio",
|
||||
"buy",
|
||||
"005930:tv",
|
||||
"--user",
|
||||
"alice",
|
||||
"--quantity",
|
||||
"2",
|
||||
"--price",
|
||||
@@ -572,18 +498,16 @@ def test_cli_create_buy_and_delete_flow() -> None:
|
||||
],
|
||||
)
|
||||
assert repo.entries[0].action == "BUY"
|
||||
assert repo._portfolio.owner_user_id == user.id
|
||||
delete_result = runner.invoke(runner.app, ["portfolio", "delete", "005930:tv", "--user", "alice", "--keep-ledger", "--force"])
|
||||
delete_result = runner.invoke(runner.app, ["portfolio", "delete", "005930:tv", "--keep-ledger", "--force"])
|
||||
|
||||
assert create_result.exit_code == 0
|
||||
assert buy_result.exit_code == 0
|
||||
assert delete_result.exit_code == 0
|
||||
assert "Deleted portfolio: 005930:tv" in delete_result.stdout # name used as identifier
|
||||
assert "Deleted portfolio: 005930:tv" in delete_result.stdout
|
||||
|
||||
|
||||
def test_cli_write_command_prompts_before_create() -> None:
|
||||
repo = FakeRepo()
|
||||
repo.create_user("alice", permission="read")
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
@@ -594,8 +518,6 @@ def test_cli_write_command_prompts_before_create() -> None:
|
||||
"005930:tv",
|
||||
"--symbol",
|
||||
"005930",
|
||||
"--user",
|
||||
"alice",
|
||||
"--cash-balance",
|
||||
"1000000",
|
||||
],
|
||||
@@ -603,12 +525,11 @@ def test_cli_write_command_prompts_before_create() -> None:
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "Create portfolio '005930:tv' for symbol 005930 (user: alice)?" in result.stdout
|
||||
assert "Create portfolio 005930:tv for symbol 005930?" in result.stdout
|
||||
|
||||
|
||||
def test_cli_write_command_can_abort() -> None:
|
||||
repo = FakeRepo()
|
||||
repo.create_user("alice", permission="read")
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
@@ -619,8 +540,6 @@ def test_cli_write_command_can_abort() -> None:
|
||||
"005930:tv",
|
||||
"--symbol",
|
||||
"005930",
|
||||
"--user",
|
||||
"alice",
|
||||
"--cash-balance",
|
||||
"1000000",
|
||||
],
|
||||
@@ -631,47 +550,6 @@ def test_cli_write_command_can_abort() -> None:
|
||||
assert repo._portfolio is None
|
||||
|
||||
|
||||
def test_cli_portfolio_create_without_user_exits_error() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "005930:tv", "--symbol", "005930", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code != 0
|
||||
assert "--user" in result.output
|
||||
|
||||
|
||||
def test_cli_portfolio_create_with_valid_user_sets_owner() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", permission="read")
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "005930:tv", "--symbol", "005930", "--user", "alice", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert repo._portfolio is not None
|
||||
assert repo._portfolio.owner_user_id == user.id
|
||||
|
||||
|
||||
def test_cli_portfolio_create_with_unknown_user_exits_error() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
["portfolio", "create", "005930:tv", "--symbol", "005930", "--user", "nobody", "--force"],
|
||||
)
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "nobody" in result.stdout
|
||||
|
||||
|
||||
def test_cli_health_check_reports_repository() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
runner = build_runner(repo)
|
||||
@@ -753,46 +631,8 @@ def test_cli_user_list_renders_table() -> None:
|
||||
result = runner.invoke(runner.app, ["user", "list"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "username: alice" in result.stdout
|
||||
assert "email: alice@example.com" in result.stdout
|
||||
assert "╭" not in result.stdout
|
||||
assert "│" not in result.stdout
|
||||
|
||||
|
||||
def test_cli_user_get_by_username_renders_plain_detail_with_service_tokens() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", email="alice@example.com", permission="admin", is_active=False)
|
||||
user.last_login_at = datetime(2026, 5, 12, 0, 0, 0, tzinfo=timezone.utc)
|
||||
repo.create_auth_token(
|
||||
user.id,
|
||||
name="svc-read",
|
||||
token_hash=hash_token("raw-read-token"),
|
||||
token_prefix=token_prefix("raw-read-token"),
|
||||
permission="read",
|
||||
token_type="service",
|
||||
)
|
||||
repo.create_auth_token(
|
||||
user.id,
|
||||
name="svc-write",
|
||||
token_hash=hash_token("raw-write-token"),
|
||||
token_prefix=token_prefix("raw-write-token"),
|
||||
permission="trade-write",
|
||||
token_type="service",
|
||||
)
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["user", "get", "alice"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "id: user-1" in result.stdout
|
||||
assert "username: alice" in result.stdout
|
||||
assert "permission: admin" in result.stdout
|
||||
assert "is_active: false" in result.stdout
|
||||
assert "last_login_at: 2026-05-12T00:00:00+00:00" in result.stdout
|
||||
assert "service_token_count: 2" in result.stdout
|
||||
assert "service_token_names: svc-read, svc-write" in result.stdout
|
||||
assert "╭" not in result.stdout
|
||||
assert "╰" not in result.stdout
|
||||
assert "Users" in result.stdout
|
||||
assert "alice" in result.stdout
|
||||
|
||||
|
||||
def test_cli_user_create_supports_json() -> None:
|
||||
@@ -829,14 +669,14 @@ def test_cli_user_update_and_delete_flow() -> None:
|
||||
|
||||
update_result = runner.invoke(
|
||||
runner.app,
|
||||
["user", "update", "alice", "--username", "alice-updated", "--force"],
|
||||
["user", "update", user.id, "--username", "alice-updated", "--force"],
|
||||
)
|
||||
delete_result = runner.invoke(runner.app, ["user", "delete", "alice-updated", "--force"])
|
||||
delete_result = runner.invoke(runner.app, ["user", "delete", user.id, "--force"])
|
||||
|
||||
assert update_result.exit_code == 0
|
||||
assert "alice-updated" in update_result.stdout
|
||||
assert delete_result.exit_code == 0
|
||||
assert "Deleted user: alice-updated" in delete_result.stdout
|
||||
assert f"Deleted user: {user.id}" in delete_result.stdout
|
||||
|
||||
def test_cli_comment_add_and_list() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
@@ -964,15 +804,11 @@ def test_cli_tag_add_and_list() -> None:
|
||||
|
||||
|
||||
def test_cli_list_supports_tag_filters() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", permission="read")
|
||||
p = _portfolio()
|
||||
p.owner_user_id = user.id
|
||||
repo._portfolio = p
|
||||
repo = FakeRepo(_portfolio())
|
||||
repo.add_tag("005930:tv", tag="grade:A")
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["portfolio", "list", "--include-tag", "grade:A", "--match-mode", "AND", "--user", "alice"])
|
||||
result = runner.invoke(runner.app, ["portfolio", "list", "--include-tag", "grade:A", "--match-mode", "AND"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "005930:tv" in result.stdout
|
||||
@@ -1206,310 +1042,3 @@ def test_cli_service_token_list_empty_message() -> None:
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "No service tokens found." in result.stdout
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# service-token rotate
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_cli_service_token_rotate_no_expiry_succeeds() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
user = repo.create_user("alice", permission="read")
|
||||
# no-expiry 토큰 발급 (expires_at=None)
|
||||
repo.create_auth_token(
|
||||
user.id,
|
||||
name="permanent",
|
||||
token_hash="oldhash",
|
||||
token_prefix="oldpref",
|
||||
permission="read",
|
||||
token_type="service",
|
||||
expires_at=None,
|
||||
runtime_id="old-runtime-id",
|
||||
)
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["service-token", "rotate", "permanent", "--force"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "token:" in result.stdout
|
||||
# token_prefix가 새 값으로 교체되었는지 확인
|
||||
rotated = repo.get_service_token_by_name("permanent")
|
||||
assert rotated is not None
|
||||
assert rotated.token_prefix != "oldpref"
|
||||
assert rotated.runtime_id != "old-runtime-id"
|
||||
|
||||
|
||||
def test_cli_service_token_rotate_unknown_name_exits_error() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["service-token", "rotate", "nonexistent", "--force"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "nonexistent" in result.stdout
|
||||
|
||||
|
||||
def test_cli_service_token_rotate_revoked_token_exits_error() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
user = repo.create_user("alice", permission="read")
|
||||
token = repo.create_auth_token(
|
||||
user.id,
|
||||
name="old-token",
|
||||
token_hash="hash",
|
||||
token_prefix="pref",
|
||||
permission="read",
|
||||
token_type="service",
|
||||
expires_at=None,
|
||||
runtime_id="rid",
|
||||
)
|
||||
repo.revoke_auth_token(user.id, token.id)
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["service-token", "rotate", "old-token", "--force"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "old-token" in result.stdout
|
||||
|
||||
|
||||
def test_cli_service_token_rotate_with_expiry_exits_error() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
user = repo.create_user("alice", permission="read")
|
||||
repo.create_auth_token(
|
||||
user.id,
|
||||
name="expiring",
|
||||
token_hash="hash",
|
||||
token_prefix="pref",
|
||||
permission="read",
|
||||
token_type="service",
|
||||
expires_at=datetime(2027, 1, 1, tzinfo=timezone.utc),
|
||||
runtime_id="rid",
|
||||
)
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["service-token", "rotate", "expiring", "--force"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "expiring" in result.stdout
|
||||
|
||||
|
||||
def test_cli_service_token_rotate_can_abort() -> None:
|
||||
repo = FakeRepo(_portfolio())
|
||||
user = repo.create_user("alice", permission="read")
|
||||
repo.create_auth_token(
|
||||
user.id,
|
||||
name="permanent",
|
||||
token_hash="hash",
|
||||
token_prefix="pref",
|
||||
permission="read",
|
||||
token_type="service",
|
||||
expires_at=None,
|
||||
runtime_id="rid",
|
||||
)
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["service-token", "rotate", "permanent"], input="n\n")
|
||||
|
||||
assert result.exit_code == 0
|
||||
# token_prefix 변경 없음
|
||||
token = repo.get_service_token_by_name("permanent")
|
||||
assert token is not None
|
||||
assert token.token_prefix == "pref"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# portfolio list --user (REQ-013)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def test_cli_portfolio_list_without_user_exits_error() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["portfolio", "list"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "--user" in result.stdout
|
||||
|
||||
|
||||
def test_cli_portfolio_list_with_valid_user_filters_by_owner() -> None:
|
||||
repo = FakeRepo()
|
||||
alice = repo.create_user("alice", permission="read")
|
||||
bob = repo.create_user("bob", permission="read")
|
||||
alice_portfolio = _portfolio()
|
||||
alice_portfolio.owner_user_id = alice.id
|
||||
repo._portfolio = alice_portfolio
|
||||
runner = build_runner(repo)
|
||||
|
||||
result_alice = runner.invoke(runner.app, ["portfolio", "list", "--user", "alice"])
|
||||
result_bob = runner.invoke(runner.app, ["portfolio", "list", "--user", "bob"])
|
||||
|
||||
assert result_alice.exit_code == 0
|
||||
assert "005930:tv" in result_alice.stdout
|
||||
assert result_bob.exit_code == 0
|
||||
assert "005930:tv" not in result_bob.stdout
|
||||
|
||||
|
||||
def test_cli_portfolio_list_with_unknown_user_exits_error() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["portfolio", "list", "--user", "nobody"])
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "nobody" in result.stdout
|
||||
|
||||
|
||||
def test_cli_portfolio_list_in_memory_mode_filters_owner() -> None:
|
||||
repo = FakeRepo()
|
||||
user = repo.create_user("alice", permission="read")
|
||||
p = _portfolio()
|
||||
p.owner_user_id = user.id
|
||||
repo._portfolio = p
|
||||
runner = build_runner(repo)
|
||||
|
||||
result = runner.invoke(runner.app, ["portfolio", "list", "--user", "alice"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "005930:tv" in result.stdout
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# credential command tests
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_TEST_ENC_KEY = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
|
||||
|
||||
|
||||
def _settings_with_enc_key() -> Settings:
|
||||
return Settings(
|
||||
db_enabled=True,
|
||||
allowed_origins_raw="http://localhost:3000",
|
||||
credential_encryption_key=_TEST_ENC_KEY,
|
||||
)
|
||||
|
||||
|
||||
def build_runner_with_enc_key(repo: FakeRepo) -> CliRunner:
|
||||
app = create_app(settings_factory=_settings_with_enc_key, repository_factory=lambda settings: repo)
|
||||
runner = CliRunner()
|
||||
runner.app = app
|
||||
return runner
|
||||
|
||||
|
||||
def test_cli_credential_set_creates_record() -> None:
|
||||
repo = FakeRepo()
|
||||
repo.create_user("alice", permission="write")
|
||||
runner = build_runner_with_enc_key(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
[
|
||||
"credential", "set",
|
||||
"--user", "alice",
|
||||
"--broker", "kis",
|
||||
"--account-ref", "12345678",
|
||||
"--app-key", "PS_APP_KEY",
|
||||
"--app-secret", "PS_APP_SECRET",
|
||||
"--force",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "kis" in result.stdout
|
||||
assert "12345678" in result.stdout
|
||||
assert "PS_APP_KEY" not in result.stdout # encrypted_payload 미출력 확인
|
||||
|
||||
|
||||
def test_cli_credential_list_hides_payload() -> None:
|
||||
repo = FakeRepo()
|
||||
repo.create_user("alice", permission="write")
|
||||
runner = build_runner_with_enc_key(repo)
|
||||
|
||||
runner.invoke(
|
||||
runner.app,
|
||||
[
|
||||
"credential", "set",
|
||||
"--user", "alice",
|
||||
"--broker", "kis",
|
||||
"--account-ref", "12345678",
|
||||
"--app-key", "MYKEY",
|
||||
"--app-secret", "MYSECRET",
|
||||
"--force",
|
||||
],
|
||||
)
|
||||
result = runner.invoke(runner.app, ["credential", "list", "--user", "alice"])
|
||||
|
||||
assert result.exit_code == 0
|
||||
assert "kis" in result.stdout
|
||||
assert "12345678" in result.stdout
|
||||
assert "MYKEY" not in result.stdout
|
||||
assert "MYSECRET" not in result.stdout
|
||||
|
||||
|
||||
def test_cli_credential_set_with_unknown_user_exits_error() -> None:
|
||||
repo = FakeRepo()
|
||||
runner = build_runner_with_enc_key(repo)
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
[
|
||||
"credential", "set",
|
||||
"--user", "ghost",
|
||||
"--broker", "kis",
|
||||
"--account-ref", "REF",
|
||||
"--app-key", "KEY",
|
||||
"--app-secret", "SECRET",
|
||||
"--force",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "ghost" in result.stdout
|
||||
|
||||
|
||||
def test_cli_credential_remove_deletes_record() -> None:
|
||||
repo = FakeRepo()
|
||||
repo.create_user("alice", permission="write")
|
||||
runner = build_runner_with_enc_key(repo)
|
||||
|
||||
runner.invoke(
|
||||
runner.app,
|
||||
[
|
||||
"credential", "set",
|
||||
"--user", "alice",
|
||||
"--broker", "kis",
|
||||
"--account-ref", "REF",
|
||||
"--app-key", "KEY",
|
||||
"--app-secret", "SECRET",
|
||||
"--force",
|
||||
],
|
||||
)
|
||||
remove_result = runner.invoke(
|
||||
runner.app,
|
||||
["credential", "remove", "--user", "alice", "--broker", "kis", "--force"],
|
||||
)
|
||||
list_result = runner.invoke(runner.app, ["credential", "list", "--user", "alice"])
|
||||
|
||||
assert remove_result.exit_code == 0
|
||||
assert list_result.exit_code == 0
|
||||
assert "kis" not in list_result.stdout
|
||||
|
||||
|
||||
def test_cli_credential_set_requires_encryption_key() -> None:
|
||||
repo = FakeRepo()
|
||||
repo.create_user("alice", permission="write")
|
||||
runner = build_runner(repo) # CREDENTIAL_ENCRYPTION_KEY 미설정 runner
|
||||
|
||||
result = runner.invoke(
|
||||
runner.app,
|
||||
[
|
||||
"credential", "set",
|
||||
"--user", "alice",
|
||||
"--broker", "kis",
|
||||
"--account-ref", "REF",
|
||||
"--app-key", "KEY",
|
||||
"--app-secret", "SECRET",
|
||||
"--force",
|
||||
],
|
||||
)
|
||||
|
||||
assert result.exit_code == 1
|
||||
assert "CREDENTIAL_ENCRYPTION_KEY" in result.stdout
|
||||
|
||||
Reference in New Issue
Block a user