360 lines
13 KiB
Python
360 lines
13 KiB
Python
|
|
"""
|
||
|
|
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
|