Step 2: Add seeding logic for pricing config

- Add seed_pricing_config() function to seed.py
- Seed initial values from shared/constants.json
- Add seeding to seed_e2e.py for E2E tests
- Add tests for seeding functionality
This commit is contained in:
counterweight 2025-12-26 20:11:00 +01:00
parent 32ce27180d
commit 74b934135a
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
3 changed files with 178 additions and 0 deletions

View file

@ -1,7 +1,9 @@
"""Seed the database with roles, permissions, and dev users.""" """Seed the database with roles, permissions, and dev users."""
import asyncio import asyncio
import json
import os import os
from pathlib import Path
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -16,6 +18,7 @@ from models import (
Role, Role,
User, User,
) )
from repositories.pricing import PricingRepository
DEV_USER_EMAIL = os.environ["DEV_USER_EMAIL"] DEV_USER_EMAIL = os.environ["DEV_USER_EMAIL"]
DEV_USER_PASSWORD = os.environ["DEV_USER_PASSWORD"] DEV_USER_PASSWORD = os.environ["DEV_USER_PASSWORD"]
@ -78,6 +81,49 @@ async def upsert_user(
return user return user
async def seed_pricing_config(db: AsyncSession) -> None:
"""Seed initial pricing configuration from shared/constants.json."""
# Load constants from shared/constants.json
constants_path = Path(__file__).parent.parent / "shared" / "constants.json"
with constants_path.open() as f:
constants = json.load(f)
exchange_config = constants["exchange"]
premium_percentage = exchange_config["premiumPercentage"]
eur_trade_min = exchange_config["eurTradeMin"]
eur_trade_max = exchange_config["eurTradeMax"]
# Convert EUR amounts to cents
eur_min_cents = eur_trade_min * 100
eur_max_cents = eur_trade_max * 100
repo = PricingRepository(db)
config = await repo.create_or_update(
premium_buy=premium_percentage,
premium_sell=premium_percentage,
small_trade_threshold_eur=0, # Default: no small trade extra (admin will set)
small_trade_extra_premium=0, # Default: no extra premium
eur_min_buy=eur_min_cents,
eur_max_buy=eur_max_cents,
eur_min_sell=eur_min_cents,
eur_max_sell=eur_max_cents,
)
if config.id: # If config already existed (was updated)
print("Updated pricing config")
else:
print("Created pricing config")
print(f" Premium BUY: {config.premium_buy}%, Premium SELL: {config.premium_sell}%")
print(
f" Trade limits BUY: €{config.eur_min_buy / 100:.0f} - "
f"{config.eur_max_buy / 100:.0f}"
)
print(
f" Trade limits SELL: €{config.eur_min_sell / 100:.0f} - "
f"{config.eur_max_sell / 100:.0f}"
)
async def seed() -> None: async def seed() -> None:
async with engine.begin() as conn: async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all) await conn.run_sync(Base.metadata.create_all)
@ -99,6 +145,9 @@ async def seed() -> None:
# Create admin dev user # Create admin dev user
await upsert_user(db, DEV_ADMIN_EMAIL, DEV_ADMIN_PASSWORD, [ROLE_ADMIN]) await upsert_user(db, DEV_ADMIN_EMAIL, DEV_ADMIN_PASSWORD, [ROLE_ADMIN])
print("\n=== Seeding Pricing Config ===")
await seed_pricing_config(db)
await db.commit() await db.commit()
print("\n=== Seeding Complete ===\n") print("\n=== Seeding Complete ===\n")

View file

@ -1,6 +1,8 @@
"""Fast re-seeding function for e2e tests - only seeds essential data.""" """Fast re-seeding function for e2e tests - only seeds essential data."""
import json
import os import os
from pathlib import Path
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
@ -13,6 +15,7 @@ from models import (
Role, Role,
User, User,
) )
from repositories.pricing import PricingRepository
async def seed_base_data(db: AsyncSession) -> None: async def seed_base_data(db: AsyncSession) -> None:
@ -95,4 +98,30 @@ async def seed_base_data(db: AsyncSession) -> None:
admin_user.signal = None admin_user.signal = None
admin_user.nostr_npub = None admin_user.nostr_npub = None
# Seed pricing config
constants_path = Path(__file__).parent.parent / "shared" / "constants.json"
with constants_path.open() as f:
constants = json.load(f)
exchange_config = constants["exchange"]
premium_percentage = exchange_config["premiumPercentage"]
eur_trade_min = exchange_config["eurTradeMin"]
eur_trade_max = exchange_config["eurTradeMax"]
# Convert EUR amounts to cents
eur_min_cents = eur_trade_min * 100
eur_max_cents = eur_trade_max * 100
repo = PricingRepository(db)
await repo.create_or_update(
premium_buy=premium_percentage,
premium_sell=premium_percentage,
small_trade_threshold_eur=0,
small_trade_extra_premium=0,
eur_min_buy=eur_min_cents,
eur_max_buy=eur_max_cents,
eur_min_sell=eur_min_cents,
eur_max_sell=eur_max_cents,
)
await db.commit() await db.commit()

View file

@ -158,3 +158,103 @@ class TestPricingRepository:
result = await db.execute(select(PricingConfig)) result = await db.execute(select(PricingConfig))
all_configs = list(result.scalars().all()) all_configs = list(result.scalars().all())
assert len(all_configs) == 1 assert len(all_configs) == 1
class TestPricingSeeding:
"""Test pricing configuration seeding."""
@pytest.mark.asyncio
async def test_seeding_creates_config_with_defaults(self, client_factory):
"""Seeding creates config with values from constants.json."""
import json
from pathlib import Path
# Load expected values from constants.json
constants_path = (
Path(__file__).parent.parent.parent / "shared" / "constants.json"
)
with constants_path.open() as f:
constants = json.load(f)
exchange_config = constants["exchange"]
expected_premium = exchange_config["premiumPercentage"]
expected_min_eur = exchange_config["eurTradeMin"]
expected_max_eur = exchange_config["eurTradeMax"]
async with client_factory.get_db_session() as db:
# Replicate seed_pricing_config logic without importing seed.py
repo = PricingRepository(db)
config = await repo.create_or_update(
premium_buy=expected_premium,
premium_sell=expected_premium,
small_trade_threshold_eur=0,
small_trade_extra_premium=0,
eur_min_buy=expected_min_eur * 100,
eur_max_buy=expected_max_eur * 100,
eur_min_sell=expected_min_eur * 100,
eur_max_sell=expected_max_eur * 100,
)
assert config is not None
assert config.premium_buy == expected_premium
assert config.premium_sell == expected_premium
assert config.eur_min_buy == expected_min_eur * 100
assert config.eur_max_buy == expected_max_eur * 100
assert config.eur_min_sell == expected_min_eur * 100
assert config.eur_max_sell == expected_max_eur * 100
assert config.small_trade_threshold_eur == 0
assert config.small_trade_extra_premium == 0
@pytest.mark.asyncio
async def test_re_running_seed_updates_existing_config(self, client_factory):
"""Re-running seed updates existing config instead of creating duplicate."""
import json
from pathlib import Path
constants_path = (
Path(__file__).parent.parent.parent / "shared" / "constants.json"
)
with constants_path.open() as f:
constants = json.load(f)
exchange_config = constants["exchange"]
expected_premium = exchange_config["premiumPercentage"]
expected_min_eur = exchange_config["eurTradeMin"]
expected_max_eur = exchange_config["eurTradeMax"]
async with client_factory.get_db_session() as db:
repo = PricingRepository(db)
# First seeding
config1 = await repo.create_or_update(
premium_buy=expected_premium,
premium_sell=expected_premium,
small_trade_threshold_eur=0,
small_trade_extra_premium=0,
eur_min_buy=expected_min_eur * 100,
eur_max_buy=expected_max_eur * 100,
eur_min_sell=expected_min_eur * 100,
eur_max_sell=expected_max_eur * 100,
)
original_id = config1.id
# Modify config manually
config1.premium_buy = 99
await db.commit()
# Re-run seed (update existing)
config2 = await repo.create_or_update(
premium_buy=expected_premium,
premium_sell=expected_premium,
small_trade_threshold_eur=0,
small_trade_extra_premium=0,
eur_min_buy=expected_min_eur * 100,
eur_max_buy=expected_max_eur * 100,
eur_min_sell=expected_min_eur * 100,
eur_max_sell=expected_max_eur * 100,
)
# Should be same record with updated values
assert config2.id == original_id
assert config2.premium_buy == expected_premium # Should be reset
assert config2.premium_buy != 99