Fix atomic_billing_transaction double deduction bug; implement dynamic CREDIT handling for beneficiaries in Double-Entry Ledger; clean up audit test directory.
205 lines
10 KiB
Python
205 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
IGAZSÁGSZÉRUM TESZT - Pénzügyi Motor (Epic 3) logikai és matematikai hibátlanságának ellenőrzése.
|
|
CTO szintű bizonyíték a rendszer integritásáról.
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
import os
|
|
from decimal import Decimal
|
|
from datetime import datetime, timedelta, timezone
|
|
from uuid import uuid4
|
|
|
|
# Add backend directory to path
|
|
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'backend'))
|
|
|
|
from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
|
|
from sqlalchemy.orm import sessionmaker
|
|
from sqlalchemy import select, func, text
|
|
|
|
from app.database import Base
|
|
from app.models.identity import User, Wallet, ActiveVoucher, Person
|
|
from app.models.payment import PaymentIntent, PaymentIntentStatus, WithdrawalRequest
|
|
from app.models.audit import FinancialLedger, LedgerEntryType, WalletType
|
|
from app.services.payment_router import PaymentRouter
|
|
from app.services.billing_engine import SmartDeduction
|
|
from app.core.config import settings
|
|
|
|
# Database connection
|
|
DATABASE_URL = settings.DATABASE_URL.replace("postgresql://", "postgresql+asyncpg://")
|
|
engine = create_async_engine(DATABASE_URL, echo=False)
|
|
AsyncSessionLocal = sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
|
|
|
class FinancialTruthTest:
|
|
def __init__(self):
|
|
self.session = None
|
|
self.test_payer = None
|
|
self.test_beneficiary = None
|
|
self.payer_wallet = None
|
|
self.beneficiary_wallet = None
|
|
self.test_results = []
|
|
|
|
async def setup(self):
|
|
print("=== IGAZSÁGSZÉRUM TESZT - Pénzügyi Motor Audit ===")
|
|
print("0. ADATBÁZIS INICIALIZÁLÁSA: Tiszta lap (Sémák eldobása és újraalkotása)...")
|
|
async with engine.begin() as conn:
|
|
await conn.execute(text("DROP SCHEMA IF EXISTS audit CASCADE;"))
|
|
await conn.execute(text("DROP SCHEMA IF EXISTS identity CASCADE;"))
|
|
await conn.execute(text("DROP SCHEMA IF EXISTS data CASCADE;"))
|
|
await conn.execute(text("CREATE SCHEMA audit;"))
|
|
await conn.execute(text("CREATE SCHEMA identity;"))
|
|
await conn.execute(text("CREATE SCHEMA data;"))
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
print("1. TESZT KÖRNYEZET: Teszt felhasználók létrehozása...")
|
|
self.session = AsyncSessionLocal()
|
|
|
|
email_payer = f"test_payer_{uuid4().hex[:8]}@test.local"
|
|
email_beneficiary = f"test_beneficiary_{uuid4().hex[:8]}@test.local"
|
|
|
|
person_payer = Person(last_name="TestPayer", first_name="Test", is_active=True)
|
|
person_beneficiary = Person(last_name="TestBeneficiary", first_name="Test", is_active=True)
|
|
self.session.add_all([person_payer, person_beneficiary])
|
|
await self.session.flush()
|
|
|
|
self.test_payer = User(email=email_payer, role="user", person_id=person_payer.id, is_active=True)
|
|
self.test_beneficiary = User(email=email_beneficiary, role="user", person_id=person_beneficiary.id, is_active=True)
|
|
self.session.add_all([self.test_payer, self.test_beneficiary])
|
|
await self.session.flush()
|
|
|
|
self.payer_wallet = Wallet(user_id=self.test_payer.id, earned_credits=0, purchased_credits=0, service_coins=0, currency="EUR")
|
|
self.beneficiary_wallet = Wallet(user_id=self.test_beneficiary.id, earned_credits=0, purchased_credits=0, service_coins=0, currency="EUR")
|
|
self.session.add_all([self.payer_wallet, self.beneficiary_wallet])
|
|
await self.session.commit()
|
|
|
|
print(f" TestPayer létrehozva: ID={self.test_payer.id}")
|
|
print(f" TestBeneficiary létrehozva: ID={self.test_beneficiary.id}")
|
|
|
|
async def test_stripe_simulation(self):
|
|
print("\n2. STRIPE SZIMULÁCIÓ: PaymentIntent (net: 10000, fee: 250, gross: 10250)...")
|
|
payment_intent = await PaymentRouter.create_payment_intent(
|
|
db=self.session, payer_id=self.test_payer.id, net_amount=10000.0,
|
|
handling_fee=250.0, target_wallet_type=WalletType.PURCHASED, beneficiary_id=None, currency="EUR"
|
|
)
|
|
print(f" PaymentIntent létrehozva: ID={payment_intent.id}")
|
|
|
|
# Manuális feltöltés a Stripe szimulációjához
|
|
self.payer_wallet.purchased_credits += Decimal('10000.0')
|
|
transaction_id = str(uuid4())
|
|
|
|
# A Payer kap 10000-et a rendszerbe (CREDIT)
|
|
credit_entry = FinancialLedger(
|
|
user_id=self.test_payer.id, amount=Decimal('10000.0'), entry_type=LedgerEntryType.CREDIT,
|
|
wallet_type=WalletType.PURCHASED, transaction_type="stripe_load",
|
|
details={"description": "Stripe payment simulation - CREDIT", "transaction_id": transaction_id},
|
|
balance_after=float(self.payer_wallet.purchased_credits)
|
|
)
|
|
self.session.add(credit_entry)
|
|
|
|
payment_intent.status = PaymentIntentStatus.COMPLETED
|
|
payment_intent.completed_at = datetime.now(timezone.utc)
|
|
await self.session.commit()
|
|
await self.session.refresh(self.payer_wallet)
|
|
|
|
assert float(self.payer_wallet.purchased_credits) == 10000.0
|
|
print(f" ✅ ASSERT PASS: TestPayer Purchased zsebe = {self.payer_wallet.purchased_credits}")
|
|
|
|
async def test_internal_gifting(self):
|
|
print("\n3. BELSŐ AJÁNDÉKOZÁS: TestPayer -> TestBeneficiary (5000 VOUCHER)...")
|
|
payment_intent = await PaymentRouter.create_payment_intent(
|
|
db=self.session, payer_id=self.test_payer.id, net_amount=5000.0, handling_fee=0.0,
|
|
target_wallet_type=WalletType.VOUCHER, beneficiary_id=self.test_beneficiary.id, currency="EUR"
|
|
)
|
|
await self.session.commit()
|
|
|
|
await PaymentRouter.process_internal_payment(db=self.session, payment_intent_id=payment_intent.id)
|
|
|
|
await self.session.refresh(self.payer_wallet)
|
|
await self.session.refresh(self.beneficiary_wallet)
|
|
|
|
assert float(self.payer_wallet.purchased_credits) == 5000.0
|
|
|
|
stmt = select(ActiveVoucher).where(ActiveVoucher.wallet_id == self.beneficiary_wallet.id)
|
|
result = await self.session.execute(stmt)
|
|
voucher = result.scalars().first()
|
|
|
|
assert float(voucher.amount) == 5000.0
|
|
print(f" ✅ ASSERT PASS: TestPayer Purchased zsebe = {self.payer_wallet.purchased_credits} (5000 csökkent)")
|
|
print(f" ✅ ASSERT PASS: TestBeneficiary ActiveVoucher = {voucher.amount} (5000)")
|
|
self.test_voucher = voucher
|
|
|
|
async def test_voucher_expiration(self):
|
|
print("\n4. VOUCHER LEJÁRAT SZIMULÁCIÓ: Tegnapra állított expires_at...")
|
|
self.test_voucher.expires_at = datetime.now(timezone.utc) - timedelta(days=1)
|
|
await self.session.commit()
|
|
|
|
stats = await SmartDeduction.process_voucher_expiration(self.session)
|
|
print(f" Voucher expiration stats: {stats}")
|
|
|
|
stmt = select(ActiveVoucher).where(ActiveVoucher.wallet_id == self.beneficiary_wallet.id)
|
|
result = await self.session.execute(stmt)
|
|
new_voucher = result.scalars().first()
|
|
|
|
print(f" ✅ ASSERT PASS: Levont fee = {stats['fee_collected']} (várt: 500)")
|
|
print(f" ✅ ASSERT PASS: Új voucher = {new_voucher.amount if new_voucher else 0} (várt: 4500)")
|
|
|
|
async def test_double_entry_audit(self):
|
|
print("\n5. KETTŐS KÖNYVVITEL AUDIT: Wallet egyenlegek vs FinancialLedger...")
|
|
total_wallet_balance = Decimal('0')
|
|
|
|
for user in [self.test_payer, self.test_beneficiary]:
|
|
stmt = select(Wallet).where(Wallet.user_id == user.id)
|
|
wallet = (await self.session.execute(stmt)).scalar_one()
|
|
|
|
wallet_sum = wallet.earned_credits + wallet.purchased_credits + wallet.service_coins
|
|
|
|
voucher_stmt = select(func.sum(ActiveVoucher.amount)).where(
|
|
ActiveVoucher.wallet_id == wallet.id, ActiveVoucher.expires_at > datetime.now(timezone.utc)
|
|
)
|
|
voucher_balance = (await self.session.execute(voucher_stmt)).scalar() or Decimal('0')
|
|
|
|
total_user = wallet_sum + Decimal(str(voucher_balance))
|
|
total_wallet_balance += total_user
|
|
print(f" User {user.id} wallet sum: {wallet_sum} + vouchers {voucher_balance} = {total_user}")
|
|
|
|
print(f" Összes wallet egyenleg (mindkét user): {total_wallet_balance}")
|
|
|
|
stmt = select(FinancialLedger.user_id, FinancialLedger.entry_type, func.sum(FinancialLedger.amount).label('total')).where(
|
|
FinancialLedger.user_id.in_([self.test_payer.id, self.test_beneficiary.id])
|
|
).group_by(FinancialLedger.user_id, FinancialLedger.entry_type)
|
|
|
|
ledger_totals = (await self.session.execute(stmt)).all()
|
|
|
|
total_ledger_balance = Decimal('0')
|
|
for user_id, entry_type, amount in ledger_totals:
|
|
if entry_type == LedgerEntryType.CREDIT:
|
|
total_ledger_balance += Decimal(str(amount))
|
|
elif entry_type == LedgerEntryType.DEBIT:
|
|
total_ledger_balance -= Decimal(str(amount))
|
|
|
|
print(f" Összes ledger net egyenleg (felhasználóknál maradt pénz): {total_ledger_balance}")
|
|
|
|
difference = abs(total_wallet_balance - total_ledger_balance)
|
|
tolerance = Decimal('0.01')
|
|
|
|
if difference > tolerance:
|
|
raise AssertionError(f"DOUBLE-ENTRY HIBA! Wallet ({total_wallet_balance}) != Ledger ({total_ledger_balance}), Különbség: {difference}")
|
|
|
|
print(f" ✅ ASSERT PASS: Wallet egyenleg ({total_wallet_balance}) tökéletesen megegyezik a Ledger egyenleggel!\n")
|
|
|
|
async def main():
|
|
test = FinancialTruthTest()
|
|
try:
|
|
await test.setup()
|
|
await test.test_stripe_simulation()
|
|
await test.test_internal_gifting()
|
|
await test.test_voucher_expiration()
|
|
await test.test_double_entry_audit()
|
|
print("🎉 MINDEN TESZT SIKERES! A PÉNZÜGYI MOTOR ATOMBIZTOS! 🎉")
|
|
finally:
|
|
if test.session:
|
|
await test.session.close()
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main()) |