pratikbackend / tests /test_comprehensive.py
Antaram's picture
Upload 96 files
7647b38 verified
Raw
History Blame Contribute Delete
29.4 kB
"""
Comprehensive Test Suite for Mirchi Trading Backend
Tests all scenarios:
- Awaak regular transactions
- Jawaak regular transactions
- Patti Awaak (no lot/weight)
- Patti Jawaak (with lot/weight - mandatory)
- Mixed bills (normal + patti items)
- Payments
- Reverts
- Party rename with historical tracking
- Multilingual (Marathi, Hindi, English) inputs
"""
import pytest
import asyncio
from httpx import AsyncClient, ASGITransport
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine, async_sessionmaker
from sqlalchemy import select
from datetime import datetime
import sys
import os
# Add backend to path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.main import app
from app.core.database import Base, get_db
from app.models import Party, MirchiType, Transaction, PattiTransaction, BillNumberSequence
from app.models.transaction import BillType
from app.models.patti_transaction import PattiBillType
from app.models.party import PartyType
# Test database URL (use SQLite for tests)
TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
@pytest.fixture(scope="session")
def event_loop():
"""Create event loop for async tests"""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
@pytest.fixture(scope="function")
async def db_session():
"""Create database session for tests"""
engine = create_async_engine(TEST_DATABASE_URL, echo=False)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async_session = async_sessionmaker(engine, expire_on_commit=False)
async with async_session() as session:
yield session
await engine.dispose()
@pytest.fixture(scope="function")
async def client(db_session):
"""Create test client"""
async def override_get_db():
yield db_session
app.dependency_overrides[get_db] = override_get_db
async with AsyncClient(transport=ASGITransport(app=app), base_url="http://test") as ac:
yield ac
app.dependency_overrides.clear()
# ============================================
# PARTY TESTS
# ============================================
class TestParty:
"""Tests for party operations"""
@pytest.mark.asyncio
async def test_create_party_english(self, client: AsyncClient):
"""Test creating party with English name"""
response = await client.post("/api/parties", json={
"name": "Ramesh Kumar",
"phone": "9876543210",
"city": "Mumbai",
"party_type": "awaak",
"notes": "Regular customer"
})
assert response.status_code == 201
data = response.json()
assert data["success"] is True
assert data["data"]["name"] == "Ramesh Kumar"
assert data["data"]["party_type"] == "awaak"
@pytest.mark.asyncio
async def test_create_party_marathi(self, client: AsyncClient):
"""Test creating party with Marathi name"""
response = await client.post("/api/parties", json={
"name": "रमेश कुमार",
"phone": "9876543210",
"city": "मुंबई",
"party_type": "jawaak",
"notes": "नियमित ग्राहक"
})
assert response.status_code == 201
data = response.json()
assert data["data"]["name"] == "रमेश कुमार"
assert data["data"]["city"] == "मुंबई"
@pytest.mark.asyncio
async def test_create_party_hindi(self, client: AsyncClient):
"""Test creating party with Hindi name"""
response = await client.post("/api/parties", json={
"name": "राम लाल",
"phone": "9876543211",
"city": "दिल्ली",
"party_type": "both",
"notes": "Hindi customer"
})
assert response.status_code == 201
data = response.json()
assert data["data"]["name"] == "राम लाल"
@pytest.mark.asyncio
async def test_rename_party(self, client: AsyncClient):
"""Test renaming party - should track history"""
# Create party
create_response = await client.post("/api/parties", json={
"name": "Old Name",
"phone": "9876543212",
"city": "Pune",
"party_type": "awaak"
})
party_id = create_response.json()["data"]["id"]
# Rename party
rename_response = await client.put(f"/api/parties/{party_id}", json={
"name": "New Name"
})
assert rename_response.status_code == 200
data = rename_response.json()
assert data["data"]["name"] == "New Name"
# Check name history
history_response = await client.get(f"/api/parties/{party_id}/name-history")
assert history_response.status_code == 200
history = history_response.json()
assert history["current_name"] == "New Name"
assert len(history["name_history"]) == 1
assert history["name_history"][0]["old_name"] == "Old Name"
assert history["name_history"][0]["new_name"] == "New Name"
# ============================================
# BILL NUMBER TESTS
# ============================================
class TestBillNumbering:
"""Tests for bill numbering system"""
@pytest.mark.asyncio
async def test_get_next_bill_number_awaak(self, client: AsyncClient):
"""Test getting next bill number for awaak"""
response = await client.get("/api/transactions/next-bill-number?bill_type=awaak")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
# Format: A2025A001
assert data["bill_number"].startswith("A")
assert "A" in data["bill_number"] # Batch A
@pytest.mark.asyncio
async def test_get_next_bill_number_jawaak(self, client: AsyncClient):
"""Test getting next bill number for jawaak"""
response = await client.get("/api/transactions/next-bill-number?bill_type=jawaak")
assert response.status_code == 200
data = response.json()
assert data["bill_number"].startswith("J")
@pytest.mark.asyncio
async def test_get_next_bill_number_patti_awaak(self, client: AsyncClient):
"""Test getting next bill number for patti_awaak"""
response = await client.get("/api/patti-transactions/next-bill-number?bill_type=patti_awaak")
assert response.status_code == 200
data = response.json()
assert data["bill_number"].startswith("PA")
@pytest.mark.asyncio
async def test_get_next_bill_number_patti_jawaak(self, client: AsyncClient):
"""Test getting next bill number for patti_jawaak"""
response = await client.get("/api/patti-transactions/next-bill-number?bill_type=patti_jawaak")
assert response.status_code == 200
data = response.json()
assert data["bill_number"].startswith("PJ")
@pytest.mark.asyncio
async def test_bill_number_info(self, client: AsyncClient):
"""Test getting bill number sequence info"""
response = await client.get("/api/transactions/bill-number-info?bill_type=awaak")
assert response.status_code == 200
data = response.json()
assert "bill_type" in data
assert "year" in data
assert "prefix" in data
assert "batches" in data
assert "total_used" in data
assert "total_remaining" in data
# ============================================
# TRANSACTION TESTS
# ============================================
class TestTransactions:
"""Tests for regular transactions"""
@pytest.mark.asyncio
async def test_create_awaak_transaction(self, client: AsyncClient):
"""Test creating awaak (purchase) transaction"""
# Create party first
party_response = await client.post("/api/parties", json={
"name": "Test Party",
"phone": "9876543213",
"city": "Nagpur",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
# Create transaction
response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 10000,
"total_amount": 10500,
"paid_amount": 5000,
"balance_amount": 5500,
"lot_number": "LOT001",
"items": [
{
"mirchi_name": "Byadgi",
"poti_count": 10,
"net_weight": 500,
"rate_per_kg": 20,
"amount": 10000,
"source": "normal"
}
],
"payments": [
{
"amount": 5000,
"mode": "cash",
"reference": "Payment 1"
}
]
})
assert response.status_code == 201
data = response.json()
assert data["success"] is True
assert data["data"]["bill_type"] == "awaak"
assert data["data"]["bill_number"].startswith("A")
assert data["data"]["party_name"] == "Test Party"
assert data["data"]["party_name_current"] == "Test Party"
@pytest.mark.asyncio
async def test_create_jawaak_transaction(self, client: AsyncClient):
"""Test creating jawaak (sales) transaction"""
# Create party first
party_response = await client.post("/api/parties", json={
"name": "Sales Party",
"phone": "9876543214",
"city": "Solapur",
"party_type": "jawaak"
})
party_id = party_response.json()["data"]["id"]
# Create transaction
response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "jawaak",
"party_id": party_id,
"subtotal": 15000,
"total_amount": 15500,
"paid_amount": 15500,
"balance_amount": 0,
"items": [
{
"mirchi_name": "Sankeshwari",
"poti_count": 15,
"net_weight": 750,
"rate_per_kg": 20,
"amount": 15000,
"source": "normal"
}
],
"payments": [
{
"amount": 15500,
"mode": "upi",
"reference": "UPI123"
}
]
})
assert response.status_code == 201
data = response.json()
assert data["data"]["bill_type"] == "jawaak"
assert data["data"]["bill_number"].startswith("J")
@pytest.mark.asyncio
async def test_create_mixed_bill(self, client: AsyncClient):
"""Test creating mixed bill (normal + patti items)"""
# Create party
party_response = await client.post("/api/parties", json={
"name": "Mixed Party",
"phone": "9876543215",
"city": "Kolhapur",
"party_type": "both"
})
party_id = party_response.json()["data"]["id"]
# Create mixed transaction
response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 20000,
"total_amount": 20500,
"paid_amount": 10000,
"balance_amount": 10500,
"items": [
{
"mirchi_name": "Byadgi",
"poti_count": 10,
"net_weight": 500,
"rate_per_kg": 20,
"amount": 10000,
"source": "normal"
},
{
"mirchi_name": "Sankeshwari",
"poti_count": 10,
"net_weight": 500,
"rate_per_kg": 20,
"amount": 10000,
"source": "patti"
}
],
"payments": [
{
"amount": 10000,
"mode": "bank",
"reference": "BANK001"
}
]
})
assert response.status_code == 201
data = response.json()
assert data["data"]["is_mixed"] is True
@pytest.mark.asyncio
async def test_transaction_with_marathi_names(self, client: AsyncClient):
"""Test transaction with Marathi mirchi names"""
# Create party with Marathi name
party_response = await client.post("/api/parties", json={
"name": "मराठी पक्ष",
"phone": "9876543216",
"city": "सांगली",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
# Create transaction with Marathi mirchi name
response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 12000,
"total_amount": 12500,
"paid_amount": 12000,
"balance_amount": 500,
"items": [
{
"mirchi_name": "ब्याडगी मिरची",
"poti_count": 12,
"net_weight": 600,
"rate_per_kg": 20,
"amount": 12000,
"source": "normal"
}
],
"payments": [
{
"amount": 12000,
"mode": "cash"
}
]
})
assert response.status_code == 201
data = response.json()
assert data["data"]["party_name"] == "मराठी पक्ष"
assert data["data"]["items"][0]["mirchi_name"] == "ब्याडगी मिरची"
# ============================================
# PATTI TRANSACTION TESTS
# ============================================
class TestPattiTransactions:
"""Tests for patti transactions"""
@pytest.mark.asyncio
async def test_create_patti_awaak(self, client: AsyncClient):
"""Test creating patti awaak (no lot/weight initially)"""
# Create party
party_response = await client.post("/api/parties", json={
"name": "Patti Holder",
"phone": "9876543217",
"city": "Latur",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
# Create patti awaak transaction (no lot required)
response = await client.post("/api/patti-transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "patti_awaak",
"party_id": party_id,
"subtotal": 8000,
"total_amount": 8500,
"paid_amount": 4000,
"balance_amount": 4500,
"expenses": {
"packing": 100,
"godown": 200,
"commission": 100,
"hamali": 50,
"gaadi_bhade": 50
},
"items": [
{
"mirchi_name": "Local Mirchi",
"poti_count": 8,
"net_weight": 400,
"rate_per_kg": 20,
"amount": 8000
}
]
})
assert response.status_code == 201
data = response.json()
assert data["data"]["bill_type"] == "patti_awaak"
assert data["data"]["bill_number"].startswith("PA")
# lot_number is optional for patti_awaak
assert data["data"].get("lot_number") is None
@pytest.mark.asyncio
async def test_create_patti_jawaak_with_lot(self, client: AsyncClient):
"""Test creating patti jawaak (lot mandatory)"""
# Create party
party_response = await client.post("/api/parties", json={
"name": "Patti Buyer",
"phone": "9876543218",
"city": "Osmanabad",
"party_type": "jawaak"
})
party_id = party_response.json()["data"]["id"]
# Create patti jawaak transaction (lot required)
response = await client.post("/api/patti-transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "patti_jawaak",
"party_id": party_id,
"subtotal": 12000,
"total_amount": 12500,
"paid_amount": 12000,
"balance_amount": 500,
"lot_number": "LOT-PJ-001", # MANDATORY for patti_jawaak
"gross_weight": 650,
"verified_net_weight": 600,
"expenses": {
"packing": 150,
"godown": 100,
"commission": 150,
"hamali": 50,
"gaadi_bhade": 50
},
"items": [
{
"mirchi_name": "Premium Mirchi",
"poti_count": 12,
"net_weight": 600,
"rate_per_kg": 20,
"amount": 12000
}
]
})
assert response.status_code == 201
data = response.json()
assert data["data"]["bill_type"] == "patti_jawaak"
assert data["data"]["bill_number"].startswith("PJ")
assert data["data"]["lot_number"] == "LOT-PJ-001"
assert data["data"]["gross_weight"] == 650
assert data["data"]["verified_net_weight"] == 600
@pytest.mark.asyncio
async def test_patti_jawaak_without_lot_fails(self, client: AsyncClient):
"""Test that patti jawaak without lot_number fails"""
# Create party
party_response = await client.post("/api/parties", json={
"name": "Test Party",
"phone": "9876543219",
"city": "Beed",
"party_type": "jawaak"
})
party_id = party_response.json()["data"]["id"]
# Try to create patti jawaak without lot (should fail)
response = await client.post("/api/patti-transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "patti_jawaak",
"party_id": party_id,
"subtotal": 10000,
"total_amount": 10500,
"paid_amount": 10000,
"balance_amount": 500,
# lot_number is MISSING - should fail
"items": [
{
"mirchi_name": "Test Mirchi",
"poti_count": 10,
"net_weight": 500,
"rate_per_kg": 20,
"amount": 10000
}
]
})
assert response.status_code == 400
assert "lot number" in response.json()["detail"].lower()
# ============================================
# PAYMENT TESTS
# ============================================
class TestPayments:
"""Tests for payment operations"""
@pytest.mark.asyncio
async def test_update_payment(self, client: AsyncClient):
"""Test updating payment for transaction"""
# Create party and transaction
party_response = await client.post("/api/parties", json={
"name": "Payment Test Party",
"phone": "9876543220",
"city": "Aurangabad",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
txn_response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 10000,
"total_amount": 10000,
"paid_amount": 5000,
"balance_amount": 5000,
"items": [
{
"mirchi_name": "Test",
"poti_count": 10,
"net_weight": 500,
"rate_per_kg": 20,
"amount": 10000,
"source": "normal"
}
],
"payments": [
{"amount": 5000, "mode": "cash"}
]
})
txn_id = txn_response.json()["data"]["id"]
# Update payment
response = await client.post(f"/api/transactions/{txn_id}/payment?amount=3000")
assert response.status_code == 200
data = response.json()
assert data["data"]["paid_amount"] == 8000
assert data["data"]["balance_amount"] == 2000
# ============================================
# REVERT TESTS
# ============================================
class TestReverts:
"""Tests for revert operations"""
@pytest.mark.asyncio
async def test_revert_transaction(self, client: AsyncClient):
"""Test reverting a transaction"""
# Create party and transaction
party_response = await client.post("/api/parties", json={
"name": "Revert Test Party",
"phone": "9876543221",
"city": "Nanded",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
txn_response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 5000,
"total_amount": 5000,
"paid_amount": 0,
"balance_amount": 5000,
"items": [
{
"mirchi_name": "Test",
"poti_count": 5,
"net_weight": 250,
"rate_per_kg": 20,
"amount": 5000,
"source": "normal"
}
],
"payments": []
})
txn_id = txn_response.json()["data"]["id"]
# Revert transaction
response = await client.post(f"/api/transactions/{txn_id}/revert")
assert response.status_code == 200
data = response.json()
assert data["success"] is True
@pytest.mark.asyncio
async def test_revert_patti_transaction(self, client: AsyncClient):
"""Test reverting a patti transaction"""
# Create party and patti transaction
party_response = await client.post("/api/parties", json={
"name": "Patti Revert Test",
"phone": "9876543222",
"city": "Parbhani",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
txn_response = await client.post("/api/patti-transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "patti_awaak",
"party_id": party_id,
"subtotal": 6000,
"total_amount": 6000,
"paid_amount": 0,
"balance_amount": 6000,
"items": [
{
"mirchi_name": "Test",
"poti_count": 6,
"net_weight": 300,
"rate_per_kg": 20,
"amount": 6000
}
]
})
txn_id = txn_response.json()["data"]["id"]
# Revert patti transaction
response = await client.post(f"/api/patti-transactions/{txn_id}/revert")
assert response.status_code == 200
# ============================================
# SEARCH TESTS
# ============================================
class TestSearch:
"""Tests for search functionality"""
@pytest.mark.asyncio
async def test_search_by_party_name(self, client: AsyncClient):
"""Test searching transactions by party name"""
# Create party with unique name
party_response = await client.post("/api/parties", json={
"name": "Unique Search Party XYZ",
"phone": "9876543223",
"city": "Jalna",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
# Create transaction
await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 1000,
"total_amount": 1000,
"paid_amount": 1000,
"balance_amount": 0,
"items": [
{
"mirchi_name": "Test",
"poti_count": 1,
"net_weight": 50,
"rate_per_kg": 20,
"amount": 1000,
"source": "normal"
}
],
"payments": [{"amount": 1000, "mode": "cash"}]
})
# Search by party name
response = await client.get("/api/transactions?search=Unique Search Party")
assert response.status_code == 200
data = response.json()
assert data["total"] >= 1
@pytest.mark.asyncio
async def test_search_marathi_text(self, client: AsyncClient):
"""Test searching with Marathi text"""
# Create party with Marathi name
party_response = await client.post("/api/parties", json={
"name": "शोध पक्ष",
"phone": "9876543224",
"city": "मुंबई",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
# Create transaction
await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 1000,
"total_amount": 1000,
"paid_amount": 1000,
"balance_amount": 0,
"items": [
{
"mirchi_name": "मिरची",
"poti_count": 1,
"net_weight": 50,
"rate_per_kg": 20,
"amount": 1000,
"source": "normal"
}
],
"payments": [{"amount": 1000, "mode": "cash"}]
})
# Search with Marathi text
response = await client.get("/api/transactions?search=शोध")
assert response.status_code == 200
# ============================================
# PARTY RENAME IMPACT TESTS
# ============================================
class TestPartyRenameImpact:
"""Tests for party rename impact on transactions"""
@pytest.mark.asyncio
async def test_rename_updates_transaction_search(self, client: AsyncClient):
"""Test that renaming party updates party_name_current in transactions"""
# Create party
party_response = await client.post("/api/parties", json={
"name": "Original Name",
"phone": "9876543225",
"city": "Pune",
"party_type": "awaak"
})
party_id = party_response.json()["data"]["id"]
# Create transaction
txn_response = await client.post("/api/transactions", json={
"bill_date": datetime.now().strftime("%Y-%m-%d"),
"bill_type": "awaak",
"party_id": party_id,
"subtotal": 1000,
"total_amount": 1000,
"paid_amount": 1000,
"balance_amount": 0,
"items": [
{
"mirchi_name": "Test",
"poti_count": 1,
"net_weight": 50,
"rate_per_kg": 20,
"amount": 1000,
"source": "normal"
}
],
"payments": [{"amount": 1000, "mode": "cash"}]
})
txn_id = txn_response.json()["data"]["id"]
# Rename party
await client.put(f"/api/parties/{party_id}", json={"name": "Renamed Party"})
# Get transaction - should have updated party_name_current
response = await client.get(f"/api/transactions/{txn_id}")
assert response.status_code == 200
data = response.json()
# Original name snapshot should remain
assert data["data"]["party_name"] == "Original Name"
# Current name should be updated for search
assert data["data"]["party_name_current"] == "Renamed Party"
# Search by new name should find the transaction
search_response = await client.get("/api/transactions?search=Renamed Party")
assert search_response.status_code == 200
assert search_response.json()["total"] >= 1
if __name__ == "__main__":
pytest.main([__file__, "-v", "--tb=short"])