mirror of
https://github.com/elisiariocouto/leggen.git
synced 2025-12-13 21:52:40 +00:00
- Remove hide_missing_ids parameter from all database functions - Remove hide_missing_ids from API routes and query parameters - Remove hide_missing_ids filtering logic from SQLite queries - Update all tests to remove hide_missing_ids assertions - Clean up codebase since internalTransactionId extraction is now fixed This functionality was added as a workaround for missing internalTransactionId values, but we've now fixed the root cause by properly extracting transaction IDs from raw data during sync, making this workaround unnecessary.
370 lines
14 KiB
Python
370 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 = [
|
|
{
|
|
"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": {"some": "data"},
|
|
},
|
|
{
|
|
"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": {"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 = [
|
|
{
|
|
"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": {"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["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 = [
|
|
{
|
|
"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": {"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&"
|
|
"limit=10&"
|
|
"offset=5"
|
|
)
|
|
|
|
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=5,
|
|
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 "0 transactions" in data["message"]
|
|
|
|
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
|