Step 3: Add admin API endpoints for pricing configuration
- Add PricingConfigResponse and PricingConfigUpdate schemas - Create PricingService with validation logic - Add GET and PUT endpoints in routes/pricing.py - Add MANAGE_PRICING permission to admin role - Register pricing router in main.py - Add comprehensive API tests for permissions and validation
This commit is contained in:
parent
74b934135a
commit
4d0dad8e2b
8 changed files with 534 additions and 0 deletions
359
backend/tests/test_pricing_api.py
Normal file
359
backend/tests/test_pricing_api.py
Normal file
|
|
@ -0,0 +1,359 @@
|
|||
"""
|
||||
Pricing API Tests
|
||||
|
||||
Tests for the admin pricing configuration endpoints.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from repositories.pricing import PricingRepository
|
||||
|
||||
|
||||
class TestPricingPermissions:
|
||||
"""Test that only admins can access pricing endpoints."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_get_pricing(self, client_factory, admin_user):
|
||||
"""Admin can access GET pricing endpoint."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.get("/api/admin/pricing")
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "premium_buy" in data
|
||||
assert "premium_sell" in data
|
||||
assert data["premium_buy"] == 5
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_regular_user_cannot_get_pricing(self, client_factory, regular_user):
|
||||
"""Regular user cannot access GET pricing endpoint."""
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
response = await client.get("/api/admin/pricing")
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthenticated_cannot_get_pricing(self, client):
|
||||
"""Unauthenticated user cannot access GET pricing endpoint."""
|
||||
response = await client.get("/api/admin/pricing")
|
||||
assert response.status_code == 401
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_can_update_pricing(self, client_factory, admin_user):
|
||||
"""Admin can access PUT pricing endpoint."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 7,
|
||||
"premium_sell": 6,
|
||||
"small_trade_threshold_eur": 25000,
|
||||
"small_trade_extra_premium": 3,
|
||||
"eur_min_buy": 15000,
|
||||
"eur_max_buy": 350000,
|
||||
"eur_min_sell": 12000,
|
||||
"eur_max_sell": 320000,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["premium_buy"] == 7
|
||||
assert data["premium_sell"] == 6
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_regular_user_cannot_update_pricing(
|
||||
self, client_factory, regular_user
|
||||
):
|
||||
"""Regular user cannot access PUT pricing endpoint."""
|
||||
async with client_factory.create(cookies=regular_user["cookies"]) as client:
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 7,
|
||||
"premium_sell": 6,
|
||||
"small_trade_threshold_eur": 25000,
|
||||
"small_trade_extra_premium": 3,
|
||||
"eur_min_buy": 15000,
|
||||
"eur_max_buy": 350000,
|
||||
"eur_min_sell": 12000,
|
||||
"eur_max_sell": 320000,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_unauthenticated_cannot_update_pricing(self, client):
|
||||
"""Unauthenticated user cannot access PUT pricing endpoint."""
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 7,
|
||||
"premium_sell": 6,
|
||||
"small_trade_threshold_eur": 25000,
|
||||
"small_trade_extra_premium": 3,
|
||||
"eur_min_buy": 15000,
|
||||
"eur_max_buy": 350000,
|
||||
"eur_min_sell": 12000,
|
||||
"eur_max_sell": 320000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 401
|
||||
|
||||
|
||||
class TestPricingValidation:
|
||||
"""Test validation of pricing configuration updates."""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_with_valid_data(self, client_factory, admin_user):
|
||||
"""Update with valid data succeeds."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 10,
|
||||
"premium_sell": 8,
|
||||
"small_trade_threshold_eur": 30000,
|
||||
"small_trade_extra_premium": 5,
|
||||
"eur_min_buy": 20000,
|
||||
"eur_max_buy": 400000,
|
||||
"eur_min_sell": 15000,
|
||||
"eur_max_sell": 350000,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["premium_buy"] == 10
|
||||
assert data["premium_sell"] == 8
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_premium_out_of_range_rejected(self, client_factory, admin_user):
|
||||
"""Premium values outside -100 to 100 are rejected."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
# Test premium_buy > 100
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 101,
|
||||
"premium_sell": 5,
|
||||
"small_trade_threshold_eur": 20000,
|
||||
"small_trade_extra_premium": 2,
|
||||
"eur_min_buy": 10000,
|
||||
"eur_max_buy": 300000,
|
||||
"eur_min_sell": 10000,
|
||||
"eur_max_sell": 300000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
# Test premium_buy < -100
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": -101,
|
||||
"premium_sell": 5,
|
||||
"small_trade_threshold_eur": 20000,
|
||||
"small_trade_extra_premium": 2,
|
||||
"eur_min_buy": 10000,
|
||||
"eur_max_buy": 300000,
|
||||
"eur_min_sell": 10000,
|
||||
"eur_max_sell": 300000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_min_greater_than_max_rejected(self, client_factory, admin_user):
|
||||
"""Min >= Max is rejected."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
# Test eur_min_buy >= eur_max_buy
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 5,
|
||||
"premium_sell": 5,
|
||||
"small_trade_threshold_eur": 20000,
|
||||
"small_trade_extra_premium": 2,
|
||||
"eur_min_buy": 300000,
|
||||
"eur_max_buy": 300000,
|
||||
"eur_min_sell": 10000,
|
||||
"eur_max_sell": 300000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_negative_amounts_rejected(self, client_factory, admin_user):
|
||||
"""Negative amounts are rejected."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
# Test negative eur_min_buy
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 5,
|
||||
"premium_sell": 5,
|
||||
"small_trade_threshold_eur": 20000,
|
||||
"small_trade_extra_premium": 2,
|
||||
"eur_min_buy": -1000,
|
||||
"eur_max_buy": 300000,
|
||||
"eur_min_sell": 10000,
|
||||
"eur_max_sell": 300000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_zero_amounts_rejected(self, client_factory, admin_user):
|
||||
"""Zero amounts are rejected."""
|
||||
# Seed pricing config first
|
||||
async with client_factory.get_db_session() as db:
|
||||
repo = PricingRepository(db)
|
||||
await repo.create_or_update(
|
||||
premium_buy=5,
|
||||
premium_sell=5,
|
||||
small_trade_threshold_eur=20000,
|
||||
small_trade_extra_premium=2,
|
||||
eur_min_buy=10000,
|
||||
eur_max_buy=300000,
|
||||
eur_min_sell=10000,
|
||||
eur_max_sell=300000,
|
||||
)
|
||||
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
# Test zero eur_min_buy
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 5,
|
||||
"premium_sell": 5,
|
||||
"small_trade_threshold_eur": 20000,
|
||||
"small_trade_extra_premium": 2,
|
||||
"eur_min_buy": 0,
|
||||
"eur_max_buy": 300000,
|
||||
"eur_min_sell": 10000,
|
||||
"eur_max_sell": 300000,
|
||||
},
|
||||
)
|
||||
assert response.status_code == 400
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_when_no_config_returns_404(self, client_factory, admin_user):
|
||||
"""GET returns 404 when no config exists."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.get("/api/admin/pricing")
|
||||
|
||||
assert response.status_code == 404
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_creates_config_if_none_exists(
|
||||
self, client_factory, admin_user
|
||||
):
|
||||
"""PUT creates config if none exists."""
|
||||
async with client_factory.create(cookies=admin_user["cookies"]) as client:
|
||||
response = await client.put(
|
||||
"/api/admin/pricing",
|
||||
json={
|
||||
"premium_buy": 5,
|
||||
"premium_sell": 5,
|
||||
"small_trade_threshold_eur": 20000,
|
||||
"small_trade_extra_premium": 2,
|
||||
"eur_min_buy": 10000,
|
||||
"eur_max_buy": 300000,
|
||||
"eur_min_sell": 10000,
|
||||
"eur_max_sell": 300000,
|
||||
},
|
||||
)
|
||||
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["premium_buy"] == 5
|
||||
|
||||
# Verify it was created
|
||||
response = await client.get("/api/admin/pricing")
|
||||
assert response.status_code == 200
|
||||
Loading…
Add table
Add a link
Reference in a new issue