mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 23:12:16 +00:00
377 lines
14 KiB
Python
377 lines
14 KiB
Python
"""Tests for transactions API endpoints."""
|
|
|
|
import pytest
|
|
from unittest.mock import patch
|
|
from datetime import datetime
|
|
|
|
|
|
@pytest.mark.api
|
|
class TestTransactionsAPI:
|
|
"""Test transaction-related API endpoints."""
|
|
|
|
def test_get_all_transactions_success(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test successful retrieval of all transactions from database."""
|
|
mock_transactions = [
|
|
{
|
|
"transactionId": "bank-txn-001", # NEW: stable bank-provided ID
|
|
"internalTransactionId": "txn-001",
|
|
"institutionId": "REVOLUT_REVOLT21",
|
|
"iban": "LT313250081177977789",
|
|
"transactionDate": datetime(2025, 9, 1, 9, 30),
|
|
"description": "Coffee Shop Payment",
|
|
"transactionValue": -10.50,
|
|
"transactionCurrency": "EUR",
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
"rawTransaction": {"transactionId": "bank-txn-001", "some": "data"},
|
|
},
|
|
{
|
|
"transactionId": "bank-txn-002", # NEW: stable bank-provided ID
|
|
"internalTransactionId": "txn-002",
|
|
"institutionId": "REVOLUT_REVOLT21",
|
|
"iban": "LT313250081177977789",
|
|
"transactionDate": datetime(2025, 9, 2, 14, 15),
|
|
"description": "Grocery Store",
|
|
"transactionValue": -45.30,
|
|
"transactionCurrency": "EUR",
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
"rawTransaction": {"transactionId": "bank-txn-002", "other": "data"},
|
|
},
|
|
]
|
|
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=mock_transactions,
|
|
),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transaction_count_from_db",
|
|
return_value=2,
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions?summary_only=true")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert len(data["data"]) == 2
|
|
|
|
# Check first transaction summary
|
|
transaction = data["data"][0]
|
|
assert transaction["internal_transaction_id"] == "txn-001"
|
|
assert transaction["amount"] == -10.50
|
|
assert transaction["currency"] == "EUR"
|
|
assert transaction["description"] == "Coffee Shop Payment"
|
|
assert transaction["status"] == "booked"
|
|
assert transaction["account_id"] == "test-account-123"
|
|
|
|
def test_get_all_transactions_full_details(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test retrieval of full transaction details from database."""
|
|
mock_transactions = [
|
|
{
|
|
"transactionId": "bank-txn-001", # NEW: stable bank-provided ID
|
|
"internalTransactionId": "txn-001",
|
|
"institutionId": "REVOLUT_REVOLT21",
|
|
"iban": "LT313250081177977789",
|
|
"transactionDate": datetime(2025, 9, 1, 9, 30),
|
|
"description": "Coffee Shop Payment",
|
|
"transactionValue": -10.50,
|
|
"transactionCurrency": "EUR",
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
"rawTransaction": {"transactionId": "bank-txn-001", "some": "raw_data"},
|
|
}
|
|
]
|
|
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=mock_transactions,
|
|
),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transaction_count_from_db",
|
|
return_value=1,
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions?summary_only=false")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert len(data["data"]) == 1
|
|
|
|
transaction = data["data"][0]
|
|
assert transaction["transaction_id"] == "bank-txn-001" # NEW: check stable ID
|
|
assert transaction["internal_transaction_id"] == "txn-001"
|
|
assert transaction["institution_id"] == "REVOLUT_REVOLT21"
|
|
assert transaction["iban"] == "LT313250081177977789"
|
|
assert "raw_transaction" in transaction
|
|
|
|
def test_get_transactions_with_filters(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test getting transactions with various filters."""
|
|
mock_transactions = [
|
|
{
|
|
"transactionId": "bank-txn-001", # NEW: stable bank-provided ID
|
|
"internalTransactionId": "txn-001",
|
|
"institutionId": "REVOLUT_REVOLT21",
|
|
"iban": "LT313250081177977789",
|
|
"transactionDate": datetime(2025, 9, 1, 9, 30),
|
|
"description": "Coffee Shop Payment",
|
|
"transactionValue": -10.50,
|
|
"transactionCurrency": "EUR",
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
"rawTransaction": {"transactionId": "bank-txn-001", "some": "data"},
|
|
}
|
|
]
|
|
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=mock_transactions,
|
|
) as mock_get_transactions,
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transaction_count_from_db",
|
|
return_value=1,
|
|
),
|
|
):
|
|
response = api_client.get(
|
|
"/api/v1/transactions?"
|
|
"account_id=test-account-123&"
|
|
"date_from=2025-09-01&"
|
|
"date_to=2025-09-02&"
|
|
"min_amount=-50.0&"
|
|
"max_amount=0.0&"
|
|
"search=Coffee&"
|
|
"page=2&"
|
|
"per_page=10"
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
|
|
# Verify the database service was called with correct filters
|
|
mock_get_transactions.assert_called_once_with(
|
|
account_id="test-account-123",
|
|
limit=10,
|
|
offset=10, # (page-1) * per_page = (2-1) * 10 = 10
|
|
date_from="2025-09-01",
|
|
date_to="2025-09-02",
|
|
min_amount=-50.0,
|
|
max_amount=0.0,
|
|
search="Coffee",
|
|
)
|
|
|
|
def test_get_transactions_empty_result(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test getting transactions when database returns empty result."""
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=[],
|
|
),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transaction_count_from_db",
|
|
return_value=0,
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
assert len(data["data"]) == 0
|
|
assert data["pagination"]["total"] == 0
|
|
assert data["pagination"]["page"] == 1
|
|
assert data["pagination"]["total_pages"] == 0
|
|
|
|
def test_get_transactions_database_error(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test handling database error when getting transactions."""
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
side_effect=Exception("Database connection failed"),
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions")
|
|
|
|
assert response.status_code == 500
|
|
assert "Failed to get transactions" in response.json()["detail"]
|
|
|
|
def test_get_transaction_stats_success(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test successful retrieval of transaction statistics from database."""
|
|
mock_transactions = [
|
|
{
|
|
"internalTransactionId": "txn-001",
|
|
"transactionDate": datetime(2025, 9, 1, 9, 30),
|
|
"transactionValue": -10.50,
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
},
|
|
{
|
|
"internalTransactionId": "txn-002",
|
|
"transactionDate": datetime(2025, 9, 2, 14, 15),
|
|
"transactionValue": 100.00,
|
|
"transactionStatus": "pending",
|
|
"accountId": "test-account-123",
|
|
},
|
|
{
|
|
"internalTransactionId": "txn-003",
|
|
"transactionDate": datetime(2025, 9, 3, 16, 45),
|
|
"transactionValue": -25.30,
|
|
"transactionStatus": "booked",
|
|
"accountId": "other-account-456",
|
|
},
|
|
]
|
|
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=mock_transactions,
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions/stats?days=30")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
|
|
stats = data["data"]
|
|
assert stats["period_days"] == 30
|
|
assert stats["total_transactions"] == 3
|
|
assert stats["booked_transactions"] == 2
|
|
assert stats["pending_transactions"] == 1
|
|
assert stats["total_income"] == 100.00
|
|
assert stats["total_expenses"] == 35.80 # abs(-10.50) + abs(-25.30)
|
|
assert stats["net_change"] == 64.20 # 100.00 - 35.80
|
|
assert stats["accounts_included"] == 2 # Two unique account IDs
|
|
|
|
# Average transaction: ((-10.50) + 100.00 + (-25.30)) / 3 = 64.20 / 3 = 21.4
|
|
expected_avg = round(64.20 / 3, 2)
|
|
assert stats["average_transaction"] == expected_avg
|
|
|
|
def test_get_transaction_stats_with_account_filter(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test getting transaction stats filtered by account."""
|
|
mock_transactions = [
|
|
{
|
|
"internalTransactionId": "txn-001",
|
|
"transactionDate": datetime(2025, 9, 1, 9, 30),
|
|
"transactionValue": -10.50,
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
}
|
|
]
|
|
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=mock_transactions,
|
|
) as mock_get_transactions,
|
|
):
|
|
response = api_client.get(
|
|
"/api/v1/transactions/stats?account_id=test-account-123"
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
|
|
# Verify the database service was called with account filter
|
|
mock_get_transactions.assert_called_once()
|
|
call_kwargs = mock_get_transactions.call_args.kwargs
|
|
assert call_kwargs["account_id"] == "test-account-123"
|
|
|
|
def test_get_transaction_stats_empty_result(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test getting stats when no transactions match criteria."""
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=[],
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions/stats")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["success"] is True
|
|
|
|
stats = data["data"]
|
|
assert stats["total_transactions"] == 0
|
|
assert stats["total_income"] == 0.0
|
|
assert stats["total_expenses"] == 0.0
|
|
assert stats["net_change"] == 0.0
|
|
assert stats["average_transaction"] == 0 # Division by zero handled
|
|
assert stats["accounts_included"] == 0
|
|
|
|
def test_get_transaction_stats_database_error(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test handling database error when getting stats."""
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
side_effect=Exception("Database connection failed"),
|
|
),
|
|
):
|
|
response = api_client.get("/api/v1/transactions/stats")
|
|
|
|
assert response.status_code == 500
|
|
assert "Failed to get transaction stats" in response.json()["detail"]
|
|
|
|
def test_get_transaction_stats_custom_period(
|
|
self, api_client, mock_config, mock_auth_token
|
|
):
|
|
"""Test getting transaction stats for custom time period."""
|
|
mock_transactions = [
|
|
{
|
|
"internalTransactionId": "txn-001",
|
|
"transactionDate": datetime(2025, 9, 1, 9, 30),
|
|
"transactionValue": -10.50,
|
|
"transactionStatus": "booked",
|
|
"accountId": "test-account-123",
|
|
}
|
|
]
|
|
|
|
with (
|
|
patch("leggend.config.config", mock_config),
|
|
patch(
|
|
"leggend.api.routes.transactions.database_service.get_transactions_from_db",
|
|
return_value=mock_transactions,
|
|
) as mock_get_transactions,
|
|
):
|
|
response = api_client.get("/api/v1/transactions/stats?days=7")
|
|
|
|
assert response.status_code == 200
|
|
data = response.json()
|
|
assert data["data"]["period_days"] == 7
|
|
|
|
# Verify the date range was calculated correctly for 7 days
|
|
mock_get_transactions.assert_called_once()
|
|
call_kwargs = mock_get_transactions.call_args.kwargs
|
|
assert "date_from" in call_kwargs
|
|
assert "date_to" in call_kwargs
|