test: add CRUD layer tests with mocked database
Tests verify Decimal values flow correctly through CRUD operations: - create_deposit passes Decimal amount to db.execute() - create_dca_payment passes Decimal fiat and exchange_rate - create_lamassu_transaction passes all 5 Decimal fields - get_client_balance_summary returns Decimal types 41 tests now pass (23 unit + 18 integration). 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
49dd4d1844
commit
d245047487
1 changed files with 242 additions and 1 deletions
|
|
@ -2,7 +2,7 @@
|
|||
Integration tests for the full transaction processing flow.
|
||||
|
||||
These tests verify that data flows correctly from:
|
||||
CSV parsing → Decimal conversion → calculations → model creation
|
||||
CSV parsing → Decimal conversion → calculations → model creation → CRUD
|
||||
|
||||
This gives us confidence that real Lamassu transactions will be
|
||||
processed correctly end-to-end.
|
||||
|
|
@ -21,6 +21,10 @@ from ..models import (
|
|||
CreateDcaPaymentData,
|
||||
CreateLamassuTransactionData,
|
||||
ClientBalanceSummary,
|
||||
CreateDepositData,
|
||||
DcaDeposit,
|
||||
DcaPayment,
|
||||
StoredLamassuTransaction,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -377,3 +381,240 @@ class TestExchangeRatePrecision:
|
|||
# This is the 0.01 discrepancy we discussed, multiplied by number of clients
|
||||
assert abs(total_fiat_distributed - tx["fiat_amount"]) < Decimal("0.05"), \
|
||||
f"Total distributed {total_fiat_distributed} vs original {tx['fiat_amount']}"
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# CRUD LAYER TESTS (with mocked database)
|
||||
# =============================================================================
|
||||
|
||||
class TestCrudLayerDecimalHandling:
|
||||
"""
|
||||
Test that CRUD operations correctly pass Decimal values to the database.
|
||||
|
||||
These tests mock the database layer to verify:
|
||||
1. Decimal values from models are passed correctly to db.execute()
|
||||
2. The correct SQL parameters include Decimal types
|
||||
"""
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_deposit_passes_decimal_amount(self):
|
||||
"""Verify create_deposit passes Decimal amount to database."""
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
# Create deposit data with Decimal
|
||||
deposit_data = CreateDepositData(
|
||||
client_id="test_client_123",
|
||||
amount=Decimal("1500.75"),
|
||||
currency="GTQ",
|
||||
notes="Test deposit"
|
||||
)
|
||||
|
||||
# Verify the model stored it as Decimal
|
||||
assert isinstance(deposit_data.amount, Decimal)
|
||||
assert deposit_data.amount == Decimal("1500.75")
|
||||
|
||||
# Mock the database
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock()
|
||||
mock_db.fetchone = AsyncMock(return_value=DcaDeposit(
|
||||
id="deposit_123",
|
||||
client_id="test_client_123",
|
||||
amount=Decimal("1500.75"),
|
||||
currency="GTQ",
|
||||
status="pending",
|
||||
notes="Test deposit",
|
||||
created_at=datetime.now(timezone.utc),
|
||||
confirmed_at=None
|
||||
))
|
||||
|
||||
with patch('satmachineadmin.crud.db', mock_db):
|
||||
from ..crud import create_deposit
|
||||
result = await create_deposit(deposit_data)
|
||||
|
||||
# Verify db.execute was called
|
||||
mock_db.execute.assert_called_once()
|
||||
|
||||
# Get the parameters passed to execute
|
||||
call_args = mock_db.execute.call_args
|
||||
params = call_args[0][1] # Second positional arg is the params dict
|
||||
|
||||
# Verify the amount parameter is Decimal
|
||||
assert isinstance(params["amount"], Decimal)
|
||||
assert params["amount"] == Decimal("1500.75")
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_dca_payment_passes_decimal_values(self):
|
||||
"""Verify create_dca_payment passes Decimal fiat and exchange_rate."""
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
# Parse a real transaction
|
||||
tx = parse_csv_like_transaction_processor(LAMASSU_CSV_DATA["5.5pct_no_discount"])
|
||||
base_sats, _, _ = calculate_commission(
|
||||
tx["crypto_amount"],
|
||||
tx["commission_percentage"],
|
||||
tx["discount"]
|
||||
)
|
||||
exchange_rate = calculate_exchange_rate(base_sats, tx["fiat_amount"])
|
||||
|
||||
# Create payment data
|
||||
client_sats = 146682 # Half of base
|
||||
client_fiat = (Decimal(client_sats) / exchange_rate).quantize(Decimal("0.01"))
|
||||
|
||||
payment_data = CreateDcaPaymentData(
|
||||
client_id="test_client",
|
||||
amount_sats=client_sats,
|
||||
amount_fiat=client_fiat,
|
||||
exchange_rate=exchange_rate,
|
||||
transaction_type="flow",
|
||||
lamassu_transaction_id="def456",
|
||||
transaction_time=tx["transaction_time"],
|
||||
)
|
||||
|
||||
# Verify model has Decimal types
|
||||
assert isinstance(payment_data.amount_fiat, Decimal)
|
||||
assert isinstance(payment_data.exchange_rate, Decimal)
|
||||
|
||||
# Mock database
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock()
|
||||
mock_db.fetchone = AsyncMock(return_value=DcaPayment(
|
||||
id="payment_123",
|
||||
client_id="test_client",
|
||||
amount_sats=client_sats,
|
||||
amount_fiat=client_fiat,
|
||||
exchange_rate=exchange_rate,
|
||||
transaction_type="flow",
|
||||
lamassu_transaction_id="def456",
|
||||
payment_hash=None,
|
||||
status="pending",
|
||||
created_at=datetime.now(timezone.utc),
|
||||
transaction_time=tx["transaction_time"],
|
||||
))
|
||||
|
||||
with patch('satmachineadmin.crud.db', mock_db):
|
||||
from ..crud import create_dca_payment
|
||||
result = await create_dca_payment(payment_data)
|
||||
|
||||
# Verify db.execute was called
|
||||
mock_db.execute.assert_called_once()
|
||||
|
||||
# Get params
|
||||
call_args = mock_db.execute.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
# Verify Decimal types in params
|
||||
assert isinstance(params["amount_fiat"], Decimal)
|
||||
assert isinstance(params["exchange_rate"], Decimal)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_lamassu_transaction_passes_all_decimals(self):
|
||||
"""Verify create_lamassu_transaction passes all Decimal fields."""
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
tx = parse_csv_like_transaction_processor(LAMASSU_CSV_DATA["8.75pct_large"])
|
||||
base_sats, commission_sats, effective = calculate_commission(
|
||||
tx["crypto_amount"],
|
||||
tx["commission_percentage"],
|
||||
tx["discount"]
|
||||
)
|
||||
exchange_rate = calculate_exchange_rate(base_sats, tx["fiat_amount"])
|
||||
|
||||
transaction_data = CreateLamassuTransactionData(
|
||||
lamassu_transaction_id=tx["transaction_id"],
|
||||
fiat_amount=tx["fiat_amount"],
|
||||
crypto_amount=tx["crypto_amount"],
|
||||
commission_percentage=tx["commission_percentage"],
|
||||
discount=tx["discount"],
|
||||
effective_commission=effective,
|
||||
commission_amount_sats=commission_sats,
|
||||
base_amount_sats=base_sats,
|
||||
exchange_rate=exchange_rate,
|
||||
crypto_code=tx["crypto_code"],
|
||||
fiat_code=tx["fiat_code"],
|
||||
device_id=tx["device_id"],
|
||||
transaction_time=tx["transaction_time"],
|
||||
)
|
||||
|
||||
# Verify all Decimal fields
|
||||
assert isinstance(transaction_data.fiat_amount, Decimal)
|
||||
assert isinstance(transaction_data.commission_percentage, Decimal)
|
||||
assert isinstance(transaction_data.discount, Decimal)
|
||||
assert isinstance(transaction_data.effective_commission, Decimal)
|
||||
assert isinstance(transaction_data.exchange_rate, Decimal)
|
||||
|
||||
# Mock database
|
||||
mock_db = AsyncMock()
|
||||
mock_db.execute = AsyncMock()
|
||||
mock_db.fetchone = AsyncMock(return_value=StoredLamassuTransaction(
|
||||
id="tx_123",
|
||||
lamassu_transaction_id=tx["transaction_id"],
|
||||
fiat_amount=tx["fiat_amount"],
|
||||
crypto_amount=tx["crypto_amount"],
|
||||
commission_percentage=tx["commission_percentage"],
|
||||
discount=tx["discount"],
|
||||
effective_commission=effective,
|
||||
commission_amount_sats=commission_sats,
|
||||
base_amount_sats=base_sats,
|
||||
exchange_rate=exchange_rate,
|
||||
crypto_code=tx["crypto_code"],
|
||||
fiat_code=tx["fiat_code"],
|
||||
device_id=tx["device_id"],
|
||||
transaction_time=tx["transaction_time"],
|
||||
processed_at=datetime.now(timezone.utc),
|
||||
clients_count=0,
|
||||
distributions_total_sats=0,
|
||||
))
|
||||
|
||||
with patch('satmachineadmin.crud.db', mock_db):
|
||||
from ..crud import create_lamassu_transaction
|
||||
result = await create_lamassu_transaction(transaction_data)
|
||||
|
||||
# Verify db.execute was called
|
||||
mock_db.execute.assert_called_once()
|
||||
|
||||
# Get params
|
||||
call_args = mock_db.execute.call_args
|
||||
params = call_args[0][1]
|
||||
|
||||
# Verify all Decimal fields in params
|
||||
assert isinstance(params["fiat_amount"], Decimal), f"fiat_amount is {type(params['fiat_amount'])}"
|
||||
assert isinstance(params["commission_percentage"], Decimal)
|
||||
assert isinstance(params["discount"], Decimal)
|
||||
assert isinstance(params["effective_commission"], Decimal)
|
||||
assert isinstance(params["exchange_rate"], Decimal)
|
||||
|
||||
# Verify values match
|
||||
assert params["fiat_amount"] == Decimal("2000")
|
||||
assert params["commission_percentage"] == Decimal("0.0875")
|
||||
assert params["base_amount_sats"] == 284322
|
||||
assert params["commission_amount_sats"] == 24878
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_client_balance_summary_returns_decimals(self):
|
||||
"""Verify get_client_balance_summary returns Decimal types."""
|
||||
from unittest.mock import patch, AsyncMock
|
||||
|
||||
# Mock database responses
|
||||
mock_db = AsyncMock()
|
||||
|
||||
# Mock deposits query result
|
||||
mock_db.fetchone = AsyncMock(side_effect=[
|
||||
# First call: deposits sum
|
||||
{"total": Decimal("5000.00"), "currency": "GTQ"},
|
||||
# Second call: payments sum
|
||||
{"total": Decimal("1234.56")},
|
||||
])
|
||||
|
||||
with patch('satmachineadmin.crud.db', mock_db):
|
||||
from ..crud import get_client_balance_summary
|
||||
result = await get_client_balance_summary("test_client")
|
||||
|
||||
# Verify result has Decimal types
|
||||
assert isinstance(result.total_deposits, Decimal)
|
||||
assert isinstance(result.total_payments, Decimal)
|
||||
assert isinstance(result.remaining_balance, Decimal)
|
||||
|
||||
# Verify values
|
||||
assert result.total_deposits == Decimal("5000.00")
|
||||
assert result.total_payments == Decimal("1234.56")
|
||||
assert result.remaining_balance == Decimal("3765.44")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue