2727from ..services import IndexServiceProvider
2828from ..tasks import logger as task_logger
2929from .factories import (
30- EthereumTxFactory ,
3130 MultisigConfirmationFactory ,
3231 MultisigTransactionFactory ,
3332 SafeContractFactory ,
@@ -741,37 +740,100 @@ def test_validate_tx_integrity(self):
741740 self .assertNotIn ("is not matching" , text )
742741 self .assertNotIn ("is not valid for multisig transaction" , text )
743742
744- def test_safe_contract_add (self ):
745- """Test adding a SafeContract entry."""
743+ @mock .patch (
744+ "safe_transaction_service.history.management.commands.safe_contract.get_auto_ethereum_client"
745+ )
746+ @mock .patch (
747+ "safe_transaction_service.history.management.commands.safe_contract.SafeEventsIndexer"
748+ )
749+ @mock .patch (
750+ "safe_transaction_service.history.management.commands.safe_contract.SafeTxProcessorProvider"
751+ )
752+ def test_safe_contract_add (
753+ self ,
754+ mock_tx_processor_provider : MagicMock ,
755+ mock_safe_events_indexer : MagicMock ,
756+ mock_get_ethereum_client : MagicMock ,
757+ ):
758+ """Test adding a SafeContract entry processes events through the indexer."""
746759 command = "safe_contract"
747760
748- address = Account .create ().address
749- ethereum_tx = EthereumTxFactory ()
750- tx_hash = ethereum_tx .tx_hash # Already a hex string
761+ safe_address = Account .create ().address
762+ tx_hash = "0x" + "ab" * 32
763+
764+ # Mock the ethereum client and its get_transaction_receipt method
765+ mock_client = MagicMock ()
766+ mock_get_ethereum_client .return_value = mock_client
767+ mock_client .get_transaction_receipt .return_value = {
768+ "logs" : [{"logIndex" : 0 , "transactionHash" : tx_hash }],
769+ "transactionHash" : tx_hash ,
770+ }
771+
772+ # Mock indexer instance
773+ mock_indexer_instance = MagicMock ()
774+ mock_safe_events_indexer .return_value = mock_indexer_instance
775+
776+ # Mock tx processor
777+ mock_tx_processor = MagicMock ()
778+ mock_tx_processor_provider .return_value = mock_tx_processor
751779
752780 self .assertEqual (SafeContract .objects .count (), 0 )
753781
754- # Add SafeContract
782+ # Add SafeContract - command will process through indexer
755783 buf = StringIO ()
756- call_command (command , "add" , address , tx_hash , stdout = buf )
784+ call_command (command , "add" , safe_address , tx_hash , stdout = buf )
757785 output = buf .getvalue ()
758- self .assertIn (f"Created SafeContract for address={ address } " , output )
759- self .assertIn (tx_hash , output )
786+
787+ # Verify get_transaction_receipt was called
788+ mock_client .get_transaction_receipt .assert_called_once ()
789+
790+ # Verify SafeEventsIndexer was created with ignored_initiators=set()
791+ mock_safe_events_indexer .assert_called_once ()
792+ call_kwargs = mock_safe_events_indexer .call_args .kwargs
793+ self .assertEqual (call_kwargs .get ("ignored_initiators" ), set ())
794+
795+ # Verify process_elements was called on the indexer instance
796+ mock_indexer_instance .process_elements .assert_called_once_with (
797+ [{"logIndex" : 0 , "transactionHash" : tx_hash }]
798+ )
799+
800+ # Verify SafeTxProcessor.process_decoded_transactions was called
801+ mock_tx_processor .process_decoded_transactions .assert_called_once ()
802+
803+ self .assertIn ("Processing events through SafeEventsIndexer" , output )
804+ self .assertIn ("Processing decoded transactions to create SafeContract" , output )
805+
806+ @mock .patch (
807+ "safe_transaction_service.history.management.commands.safe_contract.get_auto_ethereum_client"
808+ )
809+ def test_safe_contract_add_already_exists (
810+ self , mock_get_ethereum_client : MagicMock
811+ ):
812+ """Test adding a SafeContract that already exists shows warning."""
813+ command = "safe_contract"
814+
815+ safe_contract = SafeContractFactory ()
816+ safe_address = safe_contract .address
817+ tx_hash = "0x" + "ab" * 32
818+
760819 self .assertEqual (SafeContract .objects .count (), 1 )
761- self .assertTrue (SafeContract .objects .filter (address = address ).exists ())
762820
763- # Add same address again (should show warning)
821+ # Add same address again (should show warning without fetching )
764822 buf = StringIO ()
765- call_command (command , "add" , address , tx_hash , stdout = buf )
823+ call_command (command , "add" , safe_address , tx_hash , stdout = buf )
766824 output = buf .getvalue ()
767- self .assertIn (f"SafeContract already exists for address: { address } " , output )
825+ self .assertIn (
826+ f"SafeContract already exists for address: { safe_address } " , output
827+ )
768828 self .assertEqual (SafeContract .objects .count (), 1 )
769829
830+ # Should not have called ethereum client
831+ mock_get_ethereum_client .return_value .get_transaction_receipt .assert_not_called ()
832+
770833 def test_safe_contract_add_invalid_address (self ):
771834 """Test adding with invalid address shows error."""
772835 command = "safe_contract"
773- ethereum_tx = EthereumTxFactory ()
774- tx_hash = ethereum_tx .tx_hash # Already a hex string
836+ tx_hash = "0x" + "ab" * 32
775837
776838 with self .assertRaises (CommandError ) as context :
777839 call_command (command , "add" , "invalid-address" , tx_hash )
@@ -786,15 +848,46 @@ def test_safe_contract_add_invalid_tx_hash(self):
786848 call_command (command , "add" , address , "invalid-tx-hash" )
787849 self .assertIn ("Invalid transaction hash" , str (context .exception ))
788850
789- def test_safe_contract_add_nonexistent_tx (self ):
790- """Test adding with non-existent tx hash shows error when fetching from RPC fails."""
851+ @mock .patch (
852+ "safe_transaction_service.history.management.commands.safe_contract.get_auto_ethereum_client"
853+ )
854+ def test_safe_contract_add_nonexistent_tx (
855+ self , mock_get_ethereum_client : MagicMock
856+ ):
857+ """Test adding with non-existent tx hash shows error when receipt not found."""
791858 command = "safe_contract"
792859 address = Account .create ().address
793860 fake_tx_hash = "0x" + "a" * 64
794861
862+ # Mock the ethereum client to return None for receipt
863+ mock_client = MagicMock ()
864+ mock_get_ethereum_client .return_value = mock_client
865+ mock_client .get_transaction_receipt .return_value = None
866+
795867 with self .assertRaises (CommandError ) as context :
796868 call_command (command , "add" , address , fake_tx_hash )
797- self .assertIn ("Failed to fetch transaction" , str (context .exception ))
869+ self .assertIn ("not found" , str (context .exception ))
870+
871+ @mock .patch (
872+ "safe_transaction_service.history.management.commands.safe_contract.get_auto_ethereum_client"
873+ )
874+ def test_safe_contract_add_no_logs (self , mock_get_ethereum_client : MagicMock ):
875+ """Test adding with tx that has no logs shows error."""
876+ command = "safe_contract"
877+ address = Account .create ().address
878+ tx_hash = "0x" + "ab" * 32
879+
880+ # Mock the ethereum client to return receipt with no logs
881+ mock_client = MagicMock ()
882+ mock_get_ethereum_client .return_value = mock_client
883+ mock_client .get_transaction_receipt .return_value = {
884+ "logs" : [],
885+ "transactionHash" : tx_hash ,
886+ }
887+
888+ with self .assertRaises (CommandError ) as context :
889+ call_command (command , "add" , address , tx_hash )
890+ self .assertIn ("has no logs" , str (context .exception ))
798891
799892 def test_safe_contract_remove (self ):
800893 """Test removing SafeContract entries."""
0 commit comments