@@ -577,6 +577,86 @@ def test_process_conformational_residue_level(self):
577577 results = [entry [3 ] for entry in df ]
578578 self .assertIn (3.33 , results )
579579
580+ def test_finalize_molecule_results_aggregates_and_logs_total_entropy (self ):
581+ """
582+ Tests that `_finalize_molecule_results` correctly aggregates entropy values per
583+ molecule from `molecule_data`, appends a 'Molecule Total' entry, and calls
584+ `save_dataframes_as_json` with the expected DataFrame structure.
585+ """
586+ # Setup
587+ args = MagicMock (output_file = "mock_output.json" )
588+ data_logger = DataLogger ()
589+ data_logger .molecule_data = [
590+ ("mol1" , "united_atom" , "Transvibrational" , 1.0 ),
591+ ("mol1" , "united_atom" , "Rovibrational" , 2.0 ),
592+ ("mol1" , "united_atom" , "Conformational" , 3.0 ),
593+ ("mol2" , "polymer" , "Transvibrational" , 4.0 ),
594+ ]
595+ data_logger .residue_data = []
596+
597+ manager = EntropyManager (None , args , None , data_logger , None )
598+
599+ # Patch save method
600+ data_logger .save_dataframes_as_json = MagicMock ()
601+
602+ # Execute
603+ manager ._finalize_molecule_results ()
604+
605+ # Check that totals were added
606+ totals = [
607+ entry for entry in data_logger .molecule_data if entry [1 ] == "Molecule Total"
608+ ]
609+ self .assertEqual (len (totals ), 2 )
610+
611+ # Check correct aggregation
612+ mol1_total = next (entry for entry in totals if entry [0 ] == "mol1" )[3 ]
613+ mol2_total = next (entry for entry in totals if entry [0 ] == "mol2" )[3 ]
614+ self .assertEqual (mol1_total , 6.0 )
615+ self .assertEqual (mol2_total , 4.0 )
616+
617+ # Check save was called
618+ data_logger .save_dataframes_as_json .assert_called_once ()
619+
620+ @patch ("CodeEntropy.entropy.logger" )
621+ def test_finalize_molecule_results_skips_invalid_entries (self , mock_logger ):
622+ """
623+ Tests that `_finalize_molecule_results` skips entries with non-numeric entropy
624+ values and logs a warning without raising an exception.
625+ """
626+ args = MagicMock (output_file = "mock_output.json" )
627+ data_logger = DataLogger ()
628+ data_logger .molecule_data = [
629+ ("mol1" , "united_atom" , "Transvibrational" , 1.0 ),
630+ (
631+ "mol1" ,
632+ "united_atom" ,
633+ "Rovibrational" ,
634+ "not_a_number" ,
635+ ), # Should trigger ValueError
636+ ("mol1" , "united_atom" , "Conformational" , 2.0 ),
637+ ]
638+ data_logger .residue_data = []
639+
640+ manager = EntropyManager (None , args , None , data_logger , None )
641+
642+ # Patch save method
643+ data_logger .save_dataframes_as_json = MagicMock ()
644+
645+ # Run the method
646+ manager ._finalize_molecule_results ()
647+
648+ # Check that only valid values were aggregated
649+ totals = [
650+ entry for entry in data_logger .molecule_data if entry [1 ] == "Molecule Total"
651+ ]
652+ self .assertEqual (len (totals ), 1 )
653+ self .assertEqual (totals [0 ][3 ], 3.0 ) # 1.0 + 2.0
654+
655+ # Check that a warning was logged
656+ mock_logger .warning .assert_called_once_with (
657+ "Skipping invalid entry: mol1, not_a_number"
658+ )
659+
580660
581661class TestVibrationalEntropy (unittest .TestCase ):
582662 """
0 commit comments