From 25747d7d372e291090764a6814f9d8d0b76aea3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Elisi=C3=A1rio=20Couto?= Date: Thu, 18 Sep 2025 23:42:11 +0100 Subject: [PATCH] fix(api): Prevent duplicate notifications for existing transactions during sync. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The notification system was incorrectly sending notifications for existing transactions that were being updated during sync operations. This change modifies the transaction persistence logic to only return genuinely new transactions, preventing duplicate notifications while maintaining data integrity through INSERT OR REPLACE. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- leggen/services/database_service.py | 16 ++++++++++++++-- tests/unit/test_database_service.py | 25 +++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/leggen/services/database_service.py b/leggen/services/database_service.py index 5619930..b441e93 100644 --- a/leggen/services/database_service.py +++ b/leggen/services/database_service.py @@ -693,7 +693,7 @@ class DatabaseService: # Add the display_name column cursor.execute(""" - ALTER TABLE accounts + ALTER TABLE accounts ADD COLUMN display_name TEXT """) @@ -857,6 +857,14 @@ class DatabaseService: for transaction in transactions: try: + # Check if transaction already exists before insertion + cursor.execute( + """SELECT COUNT(*) FROM transactions + WHERE accountId = ? AND transactionId = ?""", + (transaction["accountId"], transaction["transactionId"]), + ) + exists = cursor.fetchone()[0] > 0 + cursor.execute( insert_sql, ( @@ -873,7 +881,11 @@ class DatabaseService: json.dumps(transaction["rawTransaction"]), ), ) - new_transactions.append(transaction) + + # Only add to new_transactions if it didn't exist before + if not exists: + new_transactions.append(transaction) + except sqlite3.IntegrityError as e: logger.warning( f"Failed to insert transaction {transaction.get('transactionId')}: {e}" diff --git a/tests/unit/test_database_service.py b/tests/unit/test_database_service.py index 94f90f7..221d36f 100644 --- a/tests/unit/test_database_service.py +++ b/tests/unit/test_database_service.py @@ -324,6 +324,8 @@ class TestDatabaseService: with patch("sqlite3.connect") as mock_connect: mock_conn = mock_connect.return_value mock_cursor = mock_conn.cursor.return_value + # Mock fetchone to return (0,) indicating transaction doesn't exist yet + mock_cursor.fetchone.return_value = (0,) result = await database_service._persist_transactions_sqlite( "test-account-123", sample_transactions_db_format @@ -338,6 +340,29 @@ class TestDatabaseService: mock_conn.commit.assert_called_once() mock_conn.close.assert_called_once() + async def test_persist_transactions_sqlite_duplicate_detection( + self, database_service, sample_transactions_db_format + ): + """Test that existing transactions are not returned as new.""" + with patch("sqlite3.connect") as mock_connect: + mock_conn = mock_connect.return_value + mock_cursor = mock_conn.cursor.return_value + # Mock fetchone to return (1,) indicating transaction already exists + mock_cursor.fetchone.return_value = (1,) + + result = await database_service._persist_transactions_sqlite( + "test-account-123", sample_transactions_db_format + ) + + # Should return empty list since all transactions already exist + assert len(result) == 0 + + # Verify database operations still happened (INSERT OR REPLACE executed) + mock_connect.assert_called() + mock_cursor.execute.assert_called() + mock_conn.commit.assert_called_once() + mock_conn.close.assert_called_once() + async def test_persist_transactions_sqlite_error(self, database_service): """Test handling error during transaction persistence.""" with patch("sqlite3.connect") as mock_connect: