import pytest from fastapi.testclient import TestClient from app.schemas.base import PaymentMethod, TransactionType, TransactionStatus from app.schemas.models import Transaction, Payment from datetime import date, datetime from sqlmodel import Session @pytest.fixture(name="sample_transaction") def sample_transaction_fixture(session: Session, sample_partner, admin_user): """Create a sample transaction for payment testing.""" transaction = Transaction( partner_id=sample_partner.id, transcation_type=TransactionType.SALE, transaction_status=TransactionStatus.UNPAID, total_amount=1000, created_by=admin_user.id, updated_by=admin_user.id ) session.add(transaction) session.commit() session.refresh(transaction) return transaction @pytest.fixture(name="sample_payment") def sample_payment_fixture(session: Session, sample_transaction, admin_user): """Create a sample payment for testing.""" payment = Payment( transaction_id=sample_transaction.id, payment_method="cash", paid_amount=500, payment_date=date.today(), created_by=admin_user.id, updated_by=admin_user.id ) session.add(payment) session.commit() session.refresh(payment) return payment @pytest.fixture(name="multiple_payments") def multiple_payments_fixture(session: Session, sample_transaction, admin_user): """Create multiple payments for testing.""" payments = [ Payment( transaction_id=sample_transaction.id, payment_method="cash", paid_amount=300, payment_date=date.today(), created_by=admin_user.id, updated_by=admin_user.id ), Payment( transaction_id=sample_transaction.id, payment_method="momo", paid_amount=200, payment_date=date.today(), created_by=admin_user.id, updated_by=admin_user.id ), Payment( transaction_id=sample_transaction.id, payment_method="bank", paid_amount=150, payment_date=date.today(), created_by=admin_user.id, updated_by=admin_user.id ) ] for payment in payments: session.add(payment) session.commit() for payment in payments: session.refresh(payment) return payments class TestPaymentCreation: """Test payment creation endpoints.""" def test_create_payment_with_admin_access(self, client: TestClient, admin_token: str, sample_transaction): """Test payment creation with admin token.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.CASH, "paid_amount": 500, "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 201 data = response.json() assert data["transaction_id"] == sample_transaction.id assert data["payment_method"] == PaymentMethod.CASH assert data["paid_amount"] == 500 assert data["payment_date"] == "2024-01-15" assert "id" in data assert "created_by" in data assert "updated_by" in data def test_create_payment_with_write_access(self, client: TestClient, write_token: str, sample_transaction): """Test payment creation with write token.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.MOMO, "paid_amount": 750, "payment_date": "2024-01-16" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {write_token}"}) assert response.status_code == 201 data = response.json() assert data["payment_method"] == PaymentMethod.MOMO assert data["paid_amount"] == 750 def test_create_payment_with_bank_method(self, client: TestClient, admin_token: str, sample_transaction): """Test payment creation with bank payment method.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.BANK, "paid_amount": 1000, "payment_date": "2024-01-17" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 201 data = response.json() assert data["payment_method"] == PaymentMethod.BANK def test_create_payment_unauthorized(self, client: TestClient, sample_transaction): """Test payment creation without authentication.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.CASH, "paid_amount": 500, "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data) assert response.status_code == 403 def test_create_payment_read_only_forbidden(self, client: TestClient, read_only_token: str, sample_transaction): """Test payment creation with read-only access should fail.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.CASH, "paid_amount": 500, "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 403 def test_create_payment_invalid_transaction(self, client: TestClient, admin_token: str): """Test creation with non-existent transaction should fail.""" payment_data = { "transaction_id": 99999, # Non-existent transaction "payment_method": PaymentMethod.CASH, "paid_amount": 500, "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 400 assert "Transaction not found" in response.json()["detail"] class TestPaymentRetrieval: """Test payment retrieval endpoints.""" def test_get_all_payments_with_auth(self, client: TestClient, admin_token: str, multiple_payments): """Test retrieving all payments with authentication.""" response = client.get("/api/v1/payments/", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) >= 3 # At least the fixture payments def test_get_all_payments_read_only_access(self, client: TestClient, read_only_token: str, multiple_payments): """Test read-only user can retrieve payments.""" response = client.get("/api/v1/payments/", headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 200 data = response.json() assert isinstance(data, list) def test_get_payments_unauthorized(self, client: TestClient): """Test retrieving payments without authentication.""" response = client.get("/api/v1/payments/") assert response.status_code == 403 def test_get_payments_with_pagination(self, client: TestClient, admin_token: str, multiple_payments): """Test payment retrieval with pagination.""" response = client.get("/api/v1/payments/?skip=0&limit=2", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert len(data) <= 2 def test_get_single_payment_by_id(self, client: TestClient, admin_token: str, sample_payment): """Test retrieving a single payment by ID.""" response = client.get(f"/api/v1/payments/{sample_payment.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["id"] == sample_payment.id assert data["transaction_id"] == sample_payment.transaction_id assert data["payment_method"] == sample_payment.payment_method assert data["paid_amount"] == sample_payment.paid_amount def test_get_nonexistent_payment(self, client: TestClient, admin_token: str): """Test retrieving a non-existent payment.""" response = client.get("/api/v1/payments/99999", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Payment not found" in response.json()["detail"] def test_get_payments_by_transaction(self, client: TestClient, admin_token: str, multiple_payments, sample_transaction): """Test retrieving payments for specific transaction.""" response = client.get(f"/api/v1/payments/transaction/{sample_transaction.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert isinstance(data, list) assert len(data) >= 3 # All payments for this transaction for payment in data: assert payment["transaction_id"] == sample_transaction.id def test_get_payments_by_nonexistent_transaction(self, client: TestClient, admin_token: str): """Test retrieving payments for non-existent transaction.""" response = client.get("/api/v1/payments/transaction/99999", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Transaction not found" in response.json()["detail"] class TestPaymentUpdate: """Test payment update endpoints.""" def test_update_payment_with_write_access(self, client: TestClient, write_token: str, sample_payment): """Test updating payment with write access.""" update_data = { "payment_method": PaymentMethod.BANK, "paid_amount": 600 } response = client.put(f"/api/v1/payments/{sample_payment.id}", json=update_data, headers={"Authorization": f"Bearer {write_token}"}) assert response.status_code == 200 data = response.json() assert data["payment_method"] == PaymentMethod.BANK assert data["paid_amount"] == 600 assert data["transaction_id"] == sample_payment.transaction_id # Unchanged def test_update_payment_date(self, client: TestClient, admin_token: str, sample_payment): """Test updating payment date.""" update_data = { "payment_date": "2024-02-01" } response = client.put(f"/api/v1/payments/{sample_payment.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["payment_date"] == "2024-02-01" def test_update_payment_transaction_id(self, client: TestClient, admin_token: str, sample_payment, sample_partner, admin_user, session): """Test updating payment transaction ID.""" # Create another transaction new_transaction = Transaction( partner_id=sample_partner.id, transcation_type=TransactionType.SALE, transaction_status=TransactionStatus.UNPAID, total_amount=2000, created_by=admin_user.id, updated_by=admin_user.id ) session.add(new_transaction) session.commit() session.refresh(new_transaction) update_data = { "transaction_id": new_transaction.id } response = client.put(f"/api/v1/payments/{sample_payment.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 200 data = response.json() assert data["transaction_id"] == new_transaction.id def test_update_payment_read_only_forbidden(self, client: TestClient, read_only_token: str, sample_payment): """Test updating payment with read-only access should fail.""" update_data = { "paid_amount": 700 } response = client.put(f"/api/v1/payments/{sample_payment.id}", json=update_data, headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 403 def test_update_payment_invalid_transaction(self, client: TestClient, admin_token: str, sample_payment): """Test updating payment with invalid transaction should fail.""" update_data = { "transaction_id": 99999 # Non-existent transaction } response = client.put(f"/api/v1/payments/{sample_payment.id}", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 400 assert "Transaction not found" in response.json()["detail"] def test_update_nonexistent_payment(self, client: TestClient, admin_token: str): """Test updating a non-existent payment.""" update_data = { "paid_amount": 800 } response = client.put("/api/v1/payments/99999", json=update_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Payment not found" in response.json()["detail"] class TestPaymentDeletion: """Test payment deletion endpoints.""" def test_delete_payment_with_admin_access(self, client: TestClient, admin_token: str, sample_payment): """Test deleting payment with admin access.""" response = client.delete(f"/api/v1/payments/{sample_payment.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 204 # Verify payment is deleted get_response = client.get(f"/api/v1/payments/{sample_payment.id}", headers={"Authorization": f"Bearer {admin_token}"}) assert get_response.status_code == 404 def test_delete_payment_write_access_forbidden(self, client: TestClient, write_token: str, sample_payment): """Test deleting payment with write access should fail.""" response = client.delete(f"/api/v1/payments/{sample_payment.id}", headers={"Authorization": f"Bearer {write_token}"}) assert response.status_code == 403 def test_delete_payment_read_only_forbidden(self, client: TestClient, read_only_token: str, sample_payment): """Test deleting payment with read-only access should fail.""" response = client.delete(f"/api/v1/payments/{sample_payment.id}", headers={"Authorization": f"Bearer {read_only_token}"}) assert response.status_code == 403 def test_delete_nonexistent_payment(self, client: TestClient, admin_token: str): """Test deleting a non-existent payment.""" response = client.delete("/api/v1/payments/99999", headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 404 assert "Payment not found" in response.json()["detail"] def test_delete_payment_unauthorized(self, client: TestClient, sample_payment): """Test deleting payment without authentication.""" response = client.delete(f"/api/v1/payments/{sample_payment.id}") assert response.status_code == 403 class TestPaymentValidation: """Test payment data validation.""" def test_create_payment_missing_required_fields(self, client: TestClient, admin_token: str): """Test creating payment with missing required fields.""" # Missing transaction_id payment_data = { "payment_method": PaymentMethod.CASH, "paid_amount": 500, "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 422 # Validation error def test_create_payment_invalid_payment_method(self, client: TestClient, admin_token: str, sample_transaction): """Test creating payment with invalid payment method.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": "INVALID_METHOD", "paid_amount": 500, "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 422 # Validation error def test_create_payment_negative_amount(self, client: TestClient, admin_token: str, sample_transaction): """Test creating payment with negative amount.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.CASH, "paid_amount": -100, # Negative amount "payment_date": "2024-01-15" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) # This might pass depending on validation rules, but business logic should prevent it # You might want to add validation in the endpoint for this def test_create_payment_invalid_date_format(self, client: TestClient, admin_token: str, sample_transaction): """Test creating payment with invalid date format.""" payment_data = { "transaction_id": sample_transaction.id, "payment_method": PaymentMethod.CASH, "paid_amount": 500, "payment_date": "invalid-date" } response = client.post("/api/v1/payments/", json=payment_data, headers={"Authorization": f"Bearer {admin_token}"}) assert response.status_code == 422 # Validation error