diff --git a/backend/seed.py b/backend/seed.py index dbb79a7..c9cd64c 100644 --- a/backend/seed.py +++ b/backend/seed.py @@ -1,7 +1,9 @@ """Seed the database with roles, permissions, and dev users.""" import asyncio +import json import os +from pathlib import Path from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -16,6 +18,7 @@ from models import ( Role, User, ) +from repositories.pricing import PricingRepository DEV_USER_EMAIL = os.environ["DEV_USER_EMAIL"] DEV_USER_PASSWORD = os.environ["DEV_USER_PASSWORD"] @@ -78,6 +81,49 @@ async def upsert_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 with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) @@ -99,6 +145,9 @@ async def seed() -> None: # Create admin dev user 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() print("\n=== Seeding Complete ===\n") diff --git a/backend/seed_e2e.py b/backend/seed_e2e.py index d2b867d..70bbfee 100644 --- a/backend/seed_e2e.py +++ b/backend/seed_e2e.py @@ -1,6 +1,8 @@ """Fast re-seeding function for e2e tests - only seeds essential data.""" +import json import os +from pathlib import Path from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession @@ -13,6 +15,7 @@ from models import ( Role, User, ) +from repositories.pricing import PricingRepository 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.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() diff --git a/backend/tests/test_pricing_repository.py b/backend/tests/test_pricing_repository.py index 3cfa328..3cc1b1b 100644 --- a/backend/tests/test_pricing_repository.py +++ b/backend/tests/test_pricing_repository.py @@ -158,3 +158,103 @@ class TestPricingRepository: result = await db.execute(select(PricingConfig)) all_configs = list(result.scalars().all()) 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