From c8f0a103c6ccdb722bbab1ac6973827b41fddc19 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 13 Sep 2025 22:34:21 +0000 Subject: [PATCH] fix: Resolve all CI failures - linting, typing, and test issues Co-authored-by: elisiariocouto <818914+elisiariocouto@users.noreply.github.com> --- .../components/analytics/MonthlyTrends.tsx | 8 +- leggen/commands/generate_sample_db.py | 1 - leggen/database/sqlite.py | 2 +- leggen/utils/paths.py | 7 +- leggend/api/routes/accounts.py | 2 +- scripts/generate_sample_db.py | 22 ++-- tests/unit/test_analytics_fix.py | 106 ++++++++---------- tests/unit/test_configurable_paths.py | 1 - 8 files changed, 65 insertions(+), 84 deletions(-) diff --git a/frontend/src/components/analytics/MonthlyTrends.tsx b/frontend/src/components/analytics/MonthlyTrends.tsx index 7e88454..2b37952 100644 --- a/frontend/src/components/analytics/MonthlyTrends.tsx +++ b/frontend/src/components/analytics/MonthlyTrends.tsx @@ -48,7 +48,7 @@ export default function MonthlyTrends({ className, days = 365 }: MonthlyTrendsPr const monthlyMap: { [key: string]: MonthlyData } = {}; transactions.forEach((transaction) => { - const date = new Date(transaction.date); + const date = new Date(transaction.transaction_date); const monthKey = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}`; if (!monthlyMap[monthKey]) { @@ -63,10 +63,10 @@ export default function MonthlyTrends({ className, days = 365 }: MonthlyTrendsPr }; } - if (transaction.amount > 0) { - monthlyMap[monthKey].income += transaction.amount; + if (transaction.transaction_value > 0) { + monthlyMap[monthKey].income += transaction.transaction_value; } else { - monthlyMap[monthKey].expenses += Math.abs(transaction.amount); + monthlyMap[monthKey].expenses += Math.abs(transaction.transaction_value); } monthlyMap[monthKey].net = monthlyMap[monthKey].income - monthlyMap[monthKey].expenses; diff --git a/leggen/commands/generate_sample_db.py b/leggen/commands/generate_sample_db.py index a4afbb2..11fb3fc 100644 --- a/leggen/commands/generate_sample_db.py +++ b/leggen/commands/generate_sample_db.py @@ -3,7 +3,6 @@ import click from pathlib import Path -from leggen.utils.paths import path_manager @click.command() diff --git a/leggen/database/sqlite.py b/leggen/database/sqlite.py index 1c35ca9..d88cd97 100644 --- a/leggen/database/sqlite.py +++ b/leggen/database/sqlite.py @@ -581,7 +581,7 @@ def get_historical_balances(account_id=None, days=365): # Calculate historical balances by working backwards from current balance historical_balances = [] - account_running_balances = {} + account_running_balances: dict[str, dict[str, float]] = {} # Initialize running balances with current balances for (acc_id, balance_type), balance_info in current_balances.items(): diff --git a/leggen/utils/paths.py b/leggen/utils/paths.py index ef39738..3f70d97 100644 --- a/leggen/utils/paths.py +++ b/leggen/utils/paths.py @@ -1,5 +1,6 @@ """Centralized path management for Leggen.""" +import contextlib import os from pathlib import Path from typing import Optional @@ -47,12 +48,8 @@ class PathManager: db_path = self.get_config_dir() / "leggen.db" # Try to ensure the directory exists, but handle permission errors gracefully - try: + with contextlib.suppress(PermissionError, OSError): db_path.parent.mkdir(parents=True, exist_ok=True) - except (PermissionError, OSError): - # If we can't create the directory, continue anyway - # This allows tests and error cases to work as expected - pass return db_path diff --git a/leggend/api/routes/accounts.py b/leggend/api/routes/accounts.py index 817608f..c05e2d1 100644 --- a/leggend/api/routes/accounts.py +++ b/leggend/api/routes/accounts.py @@ -224,7 +224,7 @@ async def get_historical_balances( try: # Get historical balances from database historical_balances = await database_service.get_historical_balances_from_db( - account_id=account_id, days=days + account_id=account_id, days=days or 365 ) return APIResponse( diff --git a/scripts/generate_sample_db.py b/scripts/generate_sample_db.py index 4b7cfd7..1088c9a 100755 --- a/scripts/generate_sample_db.py +++ b/scripts/generate_sample_db.py @@ -1,22 +1,22 @@ #!/usr/bin/env python3 """Sample database generator for Leggen testing and development.""" -import argparse import json import random import sqlite3 import sys -import os from datetime import datetime, timedelta from pathlib import Path from typing import List, Dict, Any +import click + # Add the project root to the Python path project_root = Path(__file__).parent.parent sys.path.insert(0, str(project_root)) -import click -from leggen.utils.paths import path_manager +# Import after path setup - this is necessary for the script to work +from leggen.utils.paths import path_manager # noqa: E402 class SampleDataGenerator: @@ -464,14 +464,14 @@ class SampleDataGenerator: # Print summary click.echo("\nāœ… Sample database created successfully!") - click.echo(f"šŸ“Š Summary:") + click.echo("šŸ“Š Summary:") click.echo(f" - Accounts: {len(accounts)}") click.echo(f" - Transactions: {len(transactions)}") click.echo(f" - Balances: {len(balances)}") click.echo(f" - Database: {self.db_path}") # Show account details - click.echo(f"\nšŸ“‹ Sample accounts:") + click.echo("\nšŸ“‹ Sample accounts:") for account in accounts: institution_name = next( inst["name"] @@ -533,12 +533,12 @@ def main(database: Path, accounts: int, transactions: int, force: bool): generator.generate_sample_database(accounts, transactions) # Show usage instructions - click.echo(f"\nšŸš€ Usage instructions:") - click.echo(f"To use this sample database with leggen commands:") + click.echo("\nšŸš€ Usage instructions:") + click.echo("To use this sample database with leggen commands:") click.echo(f" export LEGGEN_DATABASE_PATH={db_path}") - click.echo(f" leggen transactions") - click.echo(f"") - click.echo(f"To use this sample database with leggend API:") + click.echo(" leggen transactions") + click.echo("") + click.echo("To use this sample database with leggend API:") click.echo(f" leggend --database {db_path}") diff --git a/tests/unit/test_analytics_fix.py b/tests/unit/test_analytics_fix.py index 3de236b..4456bb6 100644 --- a/tests/unit/test_analytics_fix.py +++ b/tests/unit/test_analytics_fix.py @@ -2,7 +2,7 @@ import pytest from datetime import datetime, timedelta -from unittest.mock import Mock, AsyncMock +from unittest.mock import Mock, AsyncMock, patch from fastapi.testclient import TestClient from leggend.main import create_app @@ -22,7 +22,7 @@ class TestAnalyticsFix: return Mock(spec=DatabaseService) @pytest.mark.asyncio - async def test_transaction_stats_uses_all_transactions(self, client, mock_database_service): + async def test_transaction_stats_uses_all_transactions(self, mock_database_service): """Test that transaction stats endpoint uses all transactions (not limited to 100)""" # Mock data for 600 transactions (simulating the issue) mock_transactions = [] @@ -40,41 +40,34 @@ class TestAnalyticsFix: mock_database_service.get_transactions_from_db = AsyncMock(return_value=mock_transactions) # Test that the endpoint calls get_transactions_from_db with limit=None - with client as test_client: - # Replace the database service in the route handler - from leggend.api.routes import transactions - original_service = transactions.database_service - transactions.database_service = mock_database_service - - try: - response = test_client.get("/api/v1/transactions/stats?days=365") - - assert response.status_code == 200 - data = response.json() - - # Verify that limit=None was passed to get all transactions - mock_database_service.get_transactions_from_db.assert_called_once() - call_args = mock_database_service.get_transactions_from_db.call_args - assert call_args.kwargs.get("limit") is None, "Stats endpoint should pass limit=None to get all transactions" - - # Verify that the response contains stats for all 600 transactions - assert data["success"] is True - stats = data["data"] - assert stats["total_transactions"] == 600, "Should process all 600 transactions, not just 100" - - # Verify calculations are correct for all transactions - expected_income = sum(txn["transactionValue"] for txn in mock_transactions if txn["transactionValue"] > 0) - expected_expenses = sum(abs(txn["transactionValue"]) for txn in mock_transactions if txn["transactionValue"] < 0) - - assert stats["total_income"] == expected_income - assert stats["total_expenses"] == expected_expenses - - finally: - # Restore original service - transactions.database_service = original_service + with patch('leggend.api.routes.transactions.database_service', mock_database_service): + app = create_app() + client = TestClient(app) + + response = client.get("/api/v1/transactions/stats?days=365") + + assert response.status_code == 200 + data = response.json() + + # Verify that limit=None was passed to get all transactions + mock_database_service.get_transactions_from_db.assert_called_once() + call_args = mock_database_service.get_transactions_from_db.call_args + assert call_args.kwargs.get("limit") is None, "Stats endpoint should pass limit=None to get all transactions" + + # Verify that the response contains stats for all 600 transactions + assert data["success"] is True + stats = data["data"] + assert stats["total_transactions"] == 600, "Should process all 600 transactions, not just 100" + + # Verify calculations are correct for all transactions + expected_income = sum(txn["transactionValue"] for txn in mock_transactions if txn["transactionValue"] > 0) + expected_expenses = sum(abs(txn["transactionValue"]) for txn in mock_transactions if txn["transactionValue"] < 0) + + assert stats["total_income"] == expected_income + assert stats["total_expenses"] == expected_expenses @pytest.mark.asyncio - async def test_analytics_endpoint_returns_all_transactions(self, client, mock_database_service): + async def test_analytics_endpoint_returns_all_transactions(self, mock_database_service): """Test that the new analytics endpoint returns all transactions without pagination""" # Mock data for 600 transactions mock_transactions = [] @@ -91,28 +84,21 @@ class TestAnalyticsFix: mock_database_service.get_transactions_from_db = AsyncMock(return_value=mock_transactions) - with client as test_client: - # Replace the database service in the route handler - from leggend.api.routes import transactions - original_service = transactions.database_service - transactions.database_service = mock_database_service - - try: - response = test_client.get("/api/v1/transactions/analytics?days=365") - - assert response.status_code == 200 - data = response.json() - - # Verify that limit=None was passed to get all transactions - mock_database_service.get_transactions_from_db.assert_called_once() - call_args = mock_database_service.get_transactions_from_db.call_args - assert call_args.kwargs.get("limit") is None, "Analytics endpoint should pass limit=None" - - # Verify that all 600 transactions are returned - assert data["success"] is True - transactions_data = data["data"] - assert len(transactions_data) == 600, "Analytics endpoint should return all 600 transactions" - - finally: - # Restore original service - transactions.database_service = original_service \ No newline at end of file + with patch('leggend.api.routes.transactions.database_service', mock_database_service): + app = create_app() + client = TestClient(app) + + response = client.get("/api/v1/transactions/analytics?days=365") + + assert response.status_code == 200 + data = response.json() + + # Verify that limit=None was passed to get all transactions + mock_database_service.get_transactions_from_db.assert_called_once() + call_args = mock_database_service.get_transactions_from_db.call_args + assert call_args.kwargs.get("limit") is None, "Analytics endpoint should pass limit=None" + + # Verify that all 600 transactions are returned + assert data["success"] is True + transactions_data = data["data"] + assert len(transactions_data) == 600, "Analytics endpoint should return all 600 transactions" \ No newline at end of file diff --git a/tests/unit/test_configurable_paths.py b/tests/unit/test_configurable_paths.py index 8e84895..f889448 100644 --- a/tests/unit/test_configurable_paths.py +++ b/tests/unit/test_configurable_paths.py @@ -13,7 +13,6 @@ from leggen.database.sqlite import persist_balances, get_balances class MockContext: """Mock context for testing.""" - pass @pytest.mark.unit