Update create_exchange endpoint to accept and validate bitcoin_transfer_method
This commit is contained in:
parent
d82829ab40
commit
28e8ba218f
2 changed files with 80 additions and 0 deletions
|
|
@ -14,6 +14,7 @@ from database import get_db
|
||||||
from date_validation import validate_date_in_range
|
from date_validation import validate_date_in_range
|
||||||
from models import (
|
from models import (
|
||||||
Availability,
|
Availability,
|
||||||
|
BitcoinTransferMethod,
|
||||||
Exchange,
|
Exchange,
|
||||||
ExchangeStatus,
|
ExchangeStatus,
|
||||||
Permission,
|
Permission,
|
||||||
|
|
@ -385,6 +386,18 @@ async def create_exchange(
|
||||||
detail=f"Invalid direction: {request.direction}. Must be 'buy' or 'sell'.",
|
detail=f"Invalid direction: {request.direction}. Must be 'buy' or 'sell'.",
|
||||||
) from None
|
) from None
|
||||||
|
|
||||||
|
# Validate bitcoin transfer method
|
||||||
|
try:
|
||||||
|
bitcoin_transfer_method = BitcoinTransferMethod(request.bitcoin_transfer_method)
|
||||||
|
except ValueError:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400,
|
||||||
|
detail=(
|
||||||
|
f"Invalid bitcoin_transfer_method: {request.bitcoin_transfer_method}. "
|
||||||
|
"Must be 'onchain' or 'lightning'."
|
||||||
|
),
|
||||||
|
) from None
|
||||||
|
|
||||||
# Validate EUR amount
|
# Validate EUR amount
|
||||||
if request.eur_amount < EUR_TRADE_MIN * 100:
|
if request.eur_amount < EUR_TRADE_MIN * 100:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
|
|
@ -468,6 +481,7 @@ async def create_exchange(
|
||||||
slot_start=request.slot_start,
|
slot_start=request.slot_start,
|
||||||
slot_end=slot_end_dt,
|
slot_end=slot_end_dt,
|
||||||
direction=direction,
|
direction=direction,
|
||||||
|
bitcoin_transfer_method=bitcoin_transfer_method,
|
||||||
eur_amount=request.eur_amount,
|
eur_amount=request.eur_amount,
|
||||||
sats_amount=sats_amount,
|
sats_amount=sats_amount,
|
||||||
market_price_eur=market_price,
|
market_price_eur=market_price,
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000, # €100 in cents
|
"eur_amount": 10000, # €100 in cents
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -187,6 +188,7 @@ class TestCreateExchange:
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["direction"] == "buy"
|
assert data["direction"] == "buy"
|
||||||
|
assert data["bitcoin_transfer_method"] == "onchain"
|
||||||
assert data["eur_amount"] == 10000
|
assert data["eur_amount"] == 10000
|
||||||
assert data["status"] == "booked"
|
assert data["status"] == "booked"
|
||||||
assert data["sats_amount"] > 0
|
assert data["sats_amount"] > 0
|
||||||
|
|
@ -207,6 +209,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T10:00:00Z",
|
"slot_start": f"{target_date}T10:00:00Z",
|
||||||
"direction": "sell",
|
"direction": "sell",
|
||||||
|
"bitcoin_transfer_method": "lightning",
|
||||||
"eur_amount": 20000, # €200 in cents
|
"eur_amount": 20000, # €200 in cents
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -214,6 +217,7 @@ class TestCreateExchange:
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert data["direction"] == "sell"
|
assert data["direction"] == "sell"
|
||||||
|
assert data["bitcoin_transfer_method"] == "lightning"
|
||||||
assert data["eur_amount"] == 20000
|
assert data["eur_amount"] == 20000
|
||||||
# For sell, agreed price is market * 0.95
|
# For sell, agreed price is market * 0.95
|
||||||
assert data["agreed_price_eur"] == pytest.approx(19000.0, rel=0.001)
|
assert data["agreed_price_eur"] == pytest.approx(19000.0, rel=0.001)
|
||||||
|
|
@ -233,6 +237,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -247,6 +252,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -268,6 +274,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "invalid",
|
"direction": "invalid",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -275,6 +282,48 @@ class TestCreateExchange:
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
assert "Invalid direction" in response.json()["detail"]
|
assert "Invalid direction" in response.json()["detail"]
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_missing_bitcoin_transfer_method_rejected(
|
||||||
|
self, client_factory, regular_user, admin_user
|
||||||
|
):
|
||||||
|
"""Missing bitcoin_transfer_method is rejected."""
|
||||||
|
target_date = await setup_availability_and_price(client_factory, admin_user)
|
||||||
|
|
||||||
|
with mock_price_fetcher(20000.0):
|
||||||
|
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||||
|
response = await client.post(
|
||||||
|
"/api/exchange",
|
||||||
|
json={
|
||||||
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
|
"direction": "buy",
|
||||||
|
"eur_amount": 10000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 422
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_invalid_bitcoin_transfer_method_rejected(
|
||||||
|
self, client_factory, regular_user, admin_user
|
||||||
|
):
|
||||||
|
"""Invalid bitcoin_transfer_method is rejected."""
|
||||||
|
target_date = await setup_availability_and_price(client_factory, admin_user)
|
||||||
|
|
||||||
|
with mock_price_fetcher(20000.0):
|
||||||
|
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||||
|
response = await client.post(
|
||||||
|
"/api/exchange",
|
||||||
|
json={
|
||||||
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "invalid",
|
||||||
|
"eur_amount": 10000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert "Invalid bitcoin_transfer_method" in response.json()["detail"]
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_amount_below_minimum_rejected(
|
async def test_amount_below_minimum_rejected(
|
||||||
self, client_factory, regular_user, admin_user
|
self, client_factory, regular_user, admin_user
|
||||||
|
|
@ -289,6 +338,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 5000, # €50, below min of €100
|
"eur_amount": 5000, # €50, below min of €100
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -310,6 +360,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 400000, # €4000, above max of €3000
|
"eur_amount": 400000, # €4000, above max of €3000
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -331,6 +382,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 11500, # €115, not multiple of €20
|
"eur_amount": 11500, # €115, not multiple of €20
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -352,6 +404,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:07:00Z", # 07 is not on 15-min boundary
|
"slot_start": f"{target_date}T09:07:00Z", # 07 is not on 15-min boundary
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -384,6 +437,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{tomorrow()}T09:00:00Z",
|
"slot_start": f"{tomorrow()}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -405,6 +459,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T20:00:00Z", # Outside 09-17
|
"slot_start": f"{target_date}T20:00:00Z", # Outside 09-17
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -421,6 +476,7 @@ class TestCreateExchange:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{tomorrow()}T09:00:00Z",
|
"slot_start": f"{tomorrow()}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -460,6 +516,7 @@ class TestUserTrades:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -468,6 +525,7 @@ class TestUserTrades:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T10:00:00Z",
|
"slot_start": f"{target_date}T10:00:00Z",
|
||||||
"direction": "sell",
|
"direction": "sell",
|
||||||
|
"bitcoin_transfer_method": "lightning",
|
||||||
"eur_amount": 20000,
|
"eur_amount": 20000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -496,6 +554,7 @@ class TestCancelTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -524,6 +583,7 @@ class TestCancelTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -567,6 +627,7 @@ class TestCancelTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -601,6 +662,7 @@ class TestCancelTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -671,6 +733,7 @@ class TestAdminUpcomingTrades:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -787,6 +850,7 @@ class TestAdminCompleteTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -920,6 +984,7 @@ class TestAdminCancelTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
@ -948,6 +1013,7 @@ class TestAdminCancelTrade:
|
||||||
json={
|
json={
|
||||||
"slot_start": f"{target_date}T09:00:00Z",
|
"slot_start": f"{target_date}T09:00:00Z",
|
||||||
"direction": "buy",
|
"direction": "buy",
|
||||||
|
"bitcoin_transfer_method": "onchain",
|
||||||
"eur_amount": 10000,
|
"eur_amount": 10000,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue