Files
stonks-oracle/tests/test_operator_approval.py
T
Celes Renata c85c0068a2 fix: clean up utcnow deprecation warnings, fix 12 failing tests, add CI/CD pipeline manifests
- Replace all datetime.utcnow() with datetime.now(tz=timezone.utc) across 8 files
- Fix 12 failing tests to match current implementation behavior
- Fix pytest_plugins in non-top-level conftest (moved to root conftest.py)
- Auto-fix 189 lint issues (import sorting, unused imports)
- Add CI/CD pipeline infrastructure (ARC, ArgoCD, Kargo manifests)
- Add values-beta.yaml and values-paper.yaml for staged deployments
- Update GitHub Actions workflow to use self-hosted-gremlin runners
- Add integration-test job to CI pipeline

Result: 1596 passed, 0 failed, 0 warnings
2026-04-18 03:59:28 +00:00

142 lines
5.1 KiB
Python

"""Tests for the operator approval workflow for live trading mode.
Validates:
- requires_approval logic for paper/live/disabled modes
- ApprovalRequest model behavior (pending, expired)
- compute_expiry calculation
- Integration with broker service process_order_job flow
"""
from datetime import datetime, timedelta, timezone
from services.risk.approval import (
ApprovalRequest,
ApprovalStatus,
compute_expiry,
requires_approval,
)
from services.risk.engine import (
OperatorApproval,
PortfolioRiskConfig,
TradingMode,
)
# ---------------------------------------------------------------------------
# requires_approval tests
# ---------------------------------------------------------------------------
class TestRequiresApproval:
def test_paper_mode_auto_approved(self):
"""Paper orders are auto-approved by default."""
config = PortfolioRiskConfig(trading_mode=TradingMode.PAPER)
assert requires_approval(config) is False
def test_paper_mode_approval_required_when_auto_approve_off(self):
"""Paper orders need approval when auto_approve_paper is False."""
config = PortfolioRiskConfig(
trading_mode=TradingMode.PAPER,
operator_approval=OperatorApproval(auto_approve_paper=False),
)
assert requires_approval(config) is True
def test_live_mode_requires_approval_by_default(self):
"""Live orders require approval by default."""
config = PortfolioRiskConfig(trading_mode=TradingMode.LIVE)
assert requires_approval(config) is True
def test_live_mode_no_approval_when_disabled(self):
"""Live orders skip approval when require_approval_for_live is False."""
config = PortfolioRiskConfig(
trading_mode=TradingMode.LIVE,
operator_approval=OperatorApproval(require_approval_for_live=False),
)
assert requires_approval(config) is False
def test_disabled_mode_never_requires_approval(self):
"""Disabled mode never requires approval (blocked upstream)."""
config = PortfolioRiskConfig(trading_mode=TradingMode.DISABLED)
assert requires_approval(config) is False
def test_override_trading_mode_parameter(self):
"""The trading_mode parameter overrides config.trading_mode."""
config = PortfolioRiskConfig(trading_mode=TradingMode.PAPER)
# Override to live — should require approval
assert requires_approval(config, trading_mode=TradingMode.LIVE) is True
# ---------------------------------------------------------------------------
# compute_expiry tests
# ---------------------------------------------------------------------------
class TestComputeExpiry:
def test_default_30_minutes(self):
now = datetime(2026, 4, 11, 12, 0, 0, tzinfo=timezone.utc)
config = PortfolioRiskConfig()
expiry = compute_expiry(config, now=now)
assert expiry == now + timedelta(minutes=30)
def test_custom_timeout(self):
now = datetime(2026, 4, 11, 12, 0, 0, tzinfo=timezone.utc)
config = PortfolioRiskConfig(
operator_approval=OperatorApproval(approval_timeout_minutes=60),
)
expiry = compute_expiry(config, now=now)
assert expiry == now + timedelta(minutes=60)
# ---------------------------------------------------------------------------
# ApprovalRequest model tests
# ---------------------------------------------------------------------------
class TestApprovalRequest:
def test_defaults(self):
req = ApprovalRequest(ticker="AAPL")
assert req.ticker == "AAPL"
assert req.status == ApprovalStatus.PENDING
assert req.is_pending is True
assert req.approval_id # auto-generated UUID
def test_is_expired_when_past_expiry(self):
past = datetime.now(timezone.utc) - timedelta(minutes=5)
req = ApprovalRequest(ticker="AAPL", expires_at=past)
assert req.is_expired is True
def test_not_expired_when_future_expiry(self):
future = datetime.now(timezone.utc) + timedelta(minutes=30)
req = ApprovalRequest(ticker="AAPL", expires_at=future)
assert req.is_expired is False
def test_approved_is_not_expired(self):
past = datetime.now(timezone.utc) - timedelta(minutes=5)
req = ApprovalRequest(
ticker="AAPL",
status=ApprovalStatus.APPROVED,
expires_at=past,
)
assert req.is_expired is False
def test_to_dict_roundtrip(self):
req = ApprovalRequest(
ticker="MSFT",
side="sell",
quantity=50.0,
estimated_value=15000.0,
recommendation_id="rec-123",
)
d = req.to_dict()
assert d["ticker"] == "MSFT"
assert d["side"] == "sell"
assert d["quantity"] == 50.0
assert d["status"] == "pending"
assert d["recommendation_id"] == "rec-123"
def test_explicit_expired_status(self):
req = ApprovalRequest(
ticker="AAPL",
status=ApprovalStatus.EXPIRED,
)
assert req.is_expired is True
assert req.is_pending is False