galaxis-po/backend/tests/e2e/test_portfolio_flow.py
머니페니 741b7fa7dd feat: add skip/limit pagination to prices, snapshots, and transactions APIs
Add paginated responses (items/total/skip/limit) to:
- GET /api/data/stocks/{ticker}/prices (default limit=365)
- GET /api/data/etfs/{ticker}/prices (default limit=365)
- GET /api/portfolios/{id}/snapshots (default limit=100)
- GET /api/portfolios/{id}/transactions (default limit=50)

Frontend: update snapshot/transaction consumers to handle new response
shape, add "Load more" button to transaction table.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-18 22:32:34 +09:00

218 lines
6.4 KiB
Python

"""
E2E tests for portfolio management flow.
"""
import pytest
from fastapi.testclient import TestClient
def test_portfolio_crud_flow(client: TestClient, auth_headers):
"""Test complete portfolio CRUD flow."""
# Create portfolio
response = client.post(
"/api/portfolios",
json={
"name": "Test Portfolio",
"portfolio_type": "pension",
},
headers=auth_headers,
)
assert response.status_code == 201
portfolio = response.json()
portfolio_id = portfolio["id"]
assert portfolio["name"] == "Test Portfolio"
assert portfolio["portfolio_type"] == "pension"
# Get portfolio list
response = client.get("/api/portfolios", headers=auth_headers)
assert response.status_code == 200
portfolios = response.json()
assert len(portfolios) == 1
assert portfolios[0]["id"] == portfolio_id
# Get single portfolio
response = client.get(f"/api/portfolios/{portfolio_id}", headers=auth_headers)
assert response.status_code == 200
assert response.json()["id"] == portfolio_id
# Update portfolio
response = client.put(
f"/api/portfolios/{portfolio_id}",
json={"name": "Updated Portfolio"},
headers=auth_headers,
)
assert response.status_code == 200
assert response.json()["name"] == "Updated Portfolio"
# Delete portfolio
response = client.delete(f"/api/portfolios/{portfolio_id}", headers=auth_headers)
assert response.status_code == 204
# Verify deleted
response = client.get(f"/api/portfolios/{portfolio_id}", headers=auth_headers)
assert response.status_code == 404
def test_targets_flow(client: TestClient, auth_headers):
"""Test portfolio target allocation flow."""
# Create portfolio
response = client.post(
"/api/portfolios",
json={"name": "Target Test Portfolio", "portfolio_type": "general"},
headers=auth_headers,
)
portfolio_id = response.json()["id"]
# Set targets
targets = [
{"ticker": "005930", "target_ratio": 40},
{"ticker": "000660", "target_ratio": 30},
{"ticker": "035420", "target_ratio": 30},
]
response = client.put(
f"/api/portfolios/{portfolio_id}/targets",
json=targets,
headers=auth_headers,
)
assert response.status_code == 200
result = response.json()
assert len(result) == 3
assert sum(float(t["target_ratio"]) for t in result) == 100
# Get targets
response = client.get(
f"/api/portfolios/{portfolio_id}/targets",
headers=auth_headers,
)
assert response.status_code == 200
assert len(response.json()) == 3
# Test invalid targets (not 100%)
invalid_targets = [
{"ticker": "005930", "target_ratio": 50},
{"ticker": "000660", "target_ratio": 30},
]
response = client.put(
f"/api/portfolios/{portfolio_id}/targets",
json=invalid_targets,
headers=auth_headers,
)
assert response.status_code == 400
def test_holdings_flow(client: TestClient, auth_headers):
"""Test portfolio holdings flow."""
# Create portfolio
response = client.post(
"/api/portfolios",
json={"name": "Holdings Test Portfolio", "portfolio_type": "general"},
headers=auth_headers,
)
portfolio_id = response.json()["id"]
# Set holdings
holdings = [
{"ticker": "005930", "quantity": 10, "avg_price": 70000},
{"ticker": "000660", "quantity": 5, "avg_price": 120000},
]
response = client.put(
f"/api/portfolios/{portfolio_id}/holdings",
json=holdings,
headers=auth_headers,
)
assert response.status_code == 200
result = response.json()
assert len(result) == 2
# Get holdings
response = client.get(
f"/api/portfolios/{portfolio_id}/holdings",
headers=auth_headers,
)
assert response.status_code == 200
assert len(response.json()) == 2
def test_transaction_flow(client: TestClient, auth_headers):
"""Test transaction recording flow."""
# Create portfolio with initial holdings
response = client.post(
"/api/portfolios",
json={"name": "Transaction Test Portfolio", "portfolio_type": "general"},
headers=auth_headers,
)
portfolio_id = response.json()["id"]
# Buy transaction
response = client.post(
f"/api/portfolios/{portfolio_id}/transactions",
json={
"ticker": "005930",
"tx_type": "buy",
"quantity": 10,
"price": 70000,
"executed_at": "2024-01-15T10:00:00",
"memo": "Initial purchase",
},
headers=auth_headers,
)
assert response.status_code == 201
tx = response.json()
assert tx["ticker"] == "005930"
assert tx["tx_type"] == "buy"
# Verify holdings updated
response = client.get(
f"/api/portfolios/{portfolio_id}/holdings",
headers=auth_headers,
)
holdings = response.json()
assert len(holdings) == 1
assert holdings[0]["ticker"] == "005930"
assert holdings[0]["quantity"] == 10
# Sell transaction
response = client.post(
f"/api/portfolios/{portfolio_id}/transactions",
json={
"ticker": "005930",
"tx_type": "sell",
"quantity": 5,
"price": 75000,
"executed_at": "2024-01-16T10:00:00",
},
headers=auth_headers,
)
assert response.status_code == 201
# Verify holdings updated
response = client.get(
f"/api/portfolios/{portfolio_id}/holdings",
headers=auth_headers,
)
holdings = response.json()
assert holdings[0]["quantity"] == 5
# Get transactions
response = client.get(
f"/api/portfolios/{portfolio_id}/transactions",
headers=auth_headers,
)
assert response.status_code == 200
txs = response.json()["items"]
assert len(txs) == 2
def test_unauthorized_access(client: TestClient, auth_headers):
"""Test that users can't access other users' portfolios."""
# Create portfolio with test user
response = client.post(
"/api/portfolios",
json={"name": "Private Portfolio", "portfolio_type": "general"},
headers=auth_headers,
)
portfolio_id = response.json()["id"]
# Try to access without auth
response = client.get(f"/api/portfolios/{portfolio_id}")
assert response.status_code == 401