From 04192799ab4e36fb9937ef260a535ba534cde8f7 Mon Sep 17 00:00:00 2001 From: counterweight Date: Tue, 23 Dec 2025 15:50:14 +0100 Subject: [PATCH] Add validation to prevent booking two trades on the same day --- backend/routes/exchange.py | 22 ++++++++++++++++++++ backend/tests/test_exchange.py | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) diff --git a/backend/routes/exchange.py b/backend/routes/exchange.py index 92d3203..086128c 100644 --- a/backend/routes/exchange.py +++ b/backend/routes/exchange.py @@ -378,6 +378,28 @@ async def create_exchange( slot_date = request.slot_start.date() validate_date_in_range(slot_date, context="book") + # Check if user already has a trade on this date + existing_trade_query = select(Exchange).where( + and_( + Exchange.user_id == current_user.id, + Exchange.slot_start >= datetime.combine(slot_date, time.min, tzinfo=UTC), + Exchange.slot_start + < datetime.combine(slot_date, time.max, tzinfo=UTC) + timedelta(days=1), + Exchange.status == ExchangeStatus.BOOKED, + ) + ) + existing_trade_result = await db.execute(existing_trade_query) + existing_trade = existing_trade_result.scalar_one_or_none() + + if existing_trade: + raise HTTPException( + status_code=400, + detail=( + f"You already have a trade booked on {slot_date.strftime('%Y-%m-%d')}. " + f"Only one trade per day is allowed. Trade ID: {existing_trade.id}" + ), + ) + # Validate direction try: direction = TradeDirection(request.direction) diff --git a/backend/tests/test_exchange.py b/backend/tests/test_exchange.py index a4e6cb5..d023fa5 100644 --- a/backend/tests/test_exchange.py +++ b/backend/tests/test_exchange.py @@ -260,6 +260,43 @@ class TestCreateExchange: assert response.status_code == 409 assert "already been booked" in response.json()["detail"] + @pytest.mark.asyncio + async def test_cannot_book_two_trades_same_day( + self, client_factory, regular_user, admin_user + ): + """Cannot book two trades on the same day.""" + target_date = await setup_availability_and_price(client_factory, admin_user) + + with mock_price_fetcher(20000.0): + # First trade + 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": "onchain", + "eur_amount": 10000, + }, + ) + assert response.status_code == 200 + + # Try to book another trade on the same day + async with client_factory.create(cookies=regular_user["cookies"]) as client: + response = await client.post( + "/api/exchange", + json={ + "slot_start": f"{target_date}T10:00:00Z", + "direction": "sell", + "bitcoin_transfer_method": "lightning", + "eur_amount": 20000, + }, + ) + + assert response.status_code == 400 + assert "already have a trade booked" in response.json()["detail"] + assert "Trade ID:" in response.json()["detail"] + @pytest.mark.asyncio async def test_invalid_direction_rejected( self, client_factory, regular_user, admin_user