diff --git a/openmmdl/tests/openmmdl_setup/test_amberscript_creator.py b/openmmdl/tests/openmmdl_setup/test_amberscript_creator.py new file mode 100644 index 00000000..2a797311 --- /dev/null +++ b/openmmdl/tests/openmmdl_setup/test_amberscript_creator.py @@ -0,0 +1,576 @@ +import pytest +from openmmdl.openmmdl_setup.amberscript_creator import AmberScriptGenerator # replace 'your_module' with the actual module name + +@pytest.fixture +def mock_data(): + """Fixture to provide mock session and uploadedFiles data.""" + return { + "session": { + "rcpType": "protRcp", + "prot_ff": "ff14SB", + "other_prot_ff_input": "other_prot_ff_input", + "dna_ff": "ff99bsc1", + "other_dna_ff_input": "other_dna_ff_input", + "rna_ff": "ff99", + "other_rna_ff_input": "other_rna_ff_input", + "carbo_ff": "glycam", + "other_carbo_ff_input": "other_carbo_ff_input", + "charge_method": "bcc", + "charge_value": "-1", + "lig_ff": "gaff", + "nmLig": True, + "spLig": True, + "addType": "addWater", + "boxType": "cube", + "dist": "10.0", + "addType": "addMembrane", + "lipid_tp": "other_lipid_tp", + "other_lipid_tp_input": "custom_lipid", + "lipid_ratio": "1", + "lipid_ff": "other_lipid_ff", + "other_lipid_ff_input": "custom_lipid_ff", + "dist2Border": "10.0", + "padDist": "5.0", + "water_ff": "tip3p", + "other_water_ff_input": "custom_water_ff" + }, + "uploadedFiles": { + "protFile": [("file1", "protein.pdb")], + "dnaFile": [("file2", "dna.pdb")], + "rnaFile": [("file3", "rna.pdb")], + "carboFile": [("file4", "carbo.pdb")], + "nmLigFile": [("file5", "ligand.pdb")], + "spLigFile": [("file6", "ligand.pdb")], + "prepcFile": [("file7", "ligand.prepc")], + "frcmodFile": [("file8", "ligand.frcmod")] + } + } + +@pytest.fixture +def base_mock_data(): + """Fixture providing mock data for different receptor types.""" + return { + "protRcp": { + "session": { + "rcpType": "protRcp", + "prot_ff": "ff14SB", + "other_prot_ff_input": "custom_ff" + }, + "uploadedFiles": { + "protFile": [["file1", "protein.pdb"]] + } + }, + "dnaRcp": { + "session": { + "rcpType": "dnaRcp", + "dna_ff": "bsc1", + "other_dna_ff_input": "custom_dna_ff" + }, + "uploadedFiles": { + "dnaFile": [["file2", "dna.pdb"]] + } + }, + "rnaRcp": { + "session": { + "rcpType": "rnaRcp", + "rna_ff": "ff99SB", + "other_rna_ff_input": "custom_rna_ff" + }, + "uploadedFiles": { + "rnaFile": [["file3", "rna.pdb"]] + } + }, + "carboRcp": { + "session": { + "rcpType": "carboRcp", + "carbo_ff": "GLYCAM", + "other_carbo_ff_input": "custom_carbo_ff" + }, + "uploadedFiles": { + "carboFile": [["file4", "carbo.pdb"]] + } + } + } + +def test_add_openmmdl_logo(mock_data): + """Test if add_openmmdl_logo correctly appends the logo to the amber_script list.""" + session, uploadedFiles = mock_data + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + # Prepare an empty amber_script list + amber_script = [] + + # Call the method + amber_script_gen.add_openmmdl_logo(amber_script) + + # Define the expected logo output + expected_logo = """ +# ,-----. .-------. .-''-. ,---. .--.,---. ,---.,---. ,---. ______ .---. +# .' .-, '. \\ _(`)_ \\ .'_ _ \\ | \\ | || \\ / || \\ / || _ `''. | ,_| +# / ,-.| \\ _ \\ | (_ o._)| / ( ` ) '| , \\ | || , \\/ , || , \\/ , || _ | ) _ \\,-./ ) +# ; \\ '_ / | :| (_,_) /. (_ o _) || |\\_ \\| || |\\_ /| || |\\_ /| ||( ''_' ) |\\ '_ '`) +# | _`,/ \\ _/ || '-.-' | (_,_)___|| _( )_\\ || _( )_/ | || _( )_/ | || . (_) `. | > (_) ) +# : ( '\\_/ \\ ;| | ' \\ .---.| (_ o _) || (_ o _) | || (_ o _) | ||(_ ._) '( . .-' +# \\ `"/ \\ ) / | | \\ `-' /| (_,_)\ || (_,_) | || (_,_) | || (_.\\.' / `-'`-'|___ +# '. \\_/``".' / ) \\ / | | | || | | || | | || .' | \\ +# '-----' `---' `'-..-' '--' '--''--' '--''--' '--''-----'` `--------` + """ + + # Verify the logo is appended + assert len(amber_script) == 1 + assert amber_script[0] == expected_logo + +def test_add_openmmdl_amber_logo(mock_data): + """Test if add_openmmdl_amber_logo correctly appends the full amber logo to the amber_script list.""" + session, uploadedFiles = mock_data + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + # Prepare an empty amber_script list + amber_script = [] + + # Call the method + amber_script_gen.add_openmmdl_amber_logo(amber_script) + + # Define the expected Amber logo output + expected_logo = """ +# _ _ +# / \\ _ __ ___ | |__ ___ _ __ +# / _ \\ | '_ ` _ \\| '_ \\ / _ \\ '__| +# / ___ \\| | | | | | |_) | __/ | +# /_/ \\_\\_| |_| |_|_.__/ \\___|_| + """ + + # Verify that the full expected logo is appended + assert len(amber_script) == 1 + assert amber_script[0] == expected_logo + + +def test_add_prot_receptor_type(base_mock_data): + """Test if add_receptor_type correctly appends commands for protein receptor.""" + data = base_mock_data["protRcp"] + amber_script_gen = AmberScriptGenerator(data["session"], data["uploadedFiles"]) + + amber_script = [] + amber_script_gen.add_receptor_type(amber_script) + + expected_output = [ + "#!/bin/bash\n", + "################################## Receptor ######################################\n", + "rcp_nm=protein # the file name of ligand without suffix `pdb`", + "rcp_ff=ff14SB", + "\n" + ] + assert amber_script == expected_output + +def test_add_dna_receptor_type(base_mock_data): + """Test if add_receptor_type correctly appends commands for DNA receptor.""" + data = base_mock_data["dnaRcp"] + amber_script_gen = AmberScriptGenerator(data["session"], data["uploadedFiles"]) + + amber_script = [] + amber_script_gen.add_receptor_type(amber_script) + + expected_output = [ + "#!/bin/bash\n", + "################################## Receptor ######################################\n", + "rcp_nm=dna # the file name of ligand without suffix `pdb`", + "rcp_ff=bsc1", + "\n" + ] + assert amber_script == expected_output + +def test_add_rna_receptor_type(base_mock_data): + """Test if add_receptor_type correctly appends commands for RNA receptor.""" + data = base_mock_data["rnaRcp"] + amber_script_gen = AmberScriptGenerator(data["session"], data["uploadedFiles"]) + + amber_script = [] + amber_script_gen.add_receptor_type(amber_script) + + expected_output = [ + "#!/bin/bash\n", + "################################## Receptor ######################################\n", + "rcp_nm=rna # the file name of ligand without suffix `pdb`", + "rcp_ff=ff99SB", + "\n" + ] + assert amber_script == expected_output + +def test_add_carbo_receptor_type(base_mock_data): + """Test if add_receptor_type correctly appends commands for carbohydrate receptor.""" + data = base_mock_data["carboRcp"] + amber_script_gen = AmberScriptGenerator(data["session"], data["uploadedFiles"]) + + amber_script = [] + amber_script_gen.add_receptor_type(amber_script) + + expected_output = [ + "#!/bin/bash\n", + "################################## Receptor ######################################\n", + "rcp_nm=carbo # the file name of ligand without suffix `pdb`", + "rcp_ff=GLYCAM", + "\n" + ] + assert amber_script == expected_output + +def test_add_clean_pdb_commands(mock_data): + """Test if add_clean_pdb_commands correctly appends commands to clean the PDB file.""" + session, uploadedFiles = mock_data["session"], mock_data["uploadedFiles"] + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + amber_script = [] + amber_script_gen.add_clean_pdb_commands(amber_script) + + expected_output = [ + "## Clean the PDB file by pdb4amber", + "pdb4amber -i ${rcp_nm}.pdb -o ${rcp_nm}_amber.pdb", + """ +## `tleap` requires that all residues and atoms have appropriate types to ensure compatibility with the specified force field. +## To avoid `tleap` failing, we delete non-essential atoms, such as hydrogens, but preserve important atoms like carbon and nitrogen within the caps residues. +## Don' worry about the missing atoms as tleap has the capability to reconstruct them automatically.""", + """awk '! ($2 ~ "(CH3|HH31|HH32|HH33)" || $3 ~ "(CH3|HH31|HH32|HH33)" )' ${rcp_nm}_amber.pdb > ${rcp_nm}_amber_f.pdb""", + "grep -v '^CONECT' ${rcp_nm}_amber_f.pdb > ${rcp_nm}_cnt_rmv.pdb\n" + ] + + assert amber_script == expected_output + + +def test_add_ligand_commands_nmLig(mock_data): + """Test if add_ligand_commands correctly appends commands for a normal ligand (nmLig).""" + session = { + "nmLig": True, + "charge_method": "bcc", + "charge_value": "-1", + "lig_ff": "gaff" + } + uploadedFiles = { + "nmLigFile": [("file5", "ligand.pdb")] + } + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + amber_script = [] + amber_script_gen.add_ligand_commands(amber_script) + + expected_output = [ + "################################## Ligand ######################################", + "# Normal Ligand that is compatible with GAFF force field", + "nmLigFile=ligand # the file name of ligand without suffix `.pdb` or `.sdf`", + "obabel ${nmLigFile}.pdb -O ${nmLigFile}.sdf -p # convert to sdf file for openmmdl_analysis, -p: add hydrogens appropriate for pH7.4", + "charge_method=bcc # refers to the charge method that antechamber will adopt", + "charge_value=-1 # Enter the net molecular charge of the ligand as integer (e.g. 1 or -2)", + "lig_ff=gaff # Ligand force field\n", + "## Clean the PDB file by pdb4amber", + "pdb4amber -i ${nmLigFile}.pdb -o ${nmLigFile}_amber.pdb\n", + "## Generate a prepc file and an additional frcmod file by `antechamber`", + "antechamber -fi pdb -fo prepc -i ${nmLigFile}_amber.pdb -o ${nmLigFile}.prepc -c ${charge_method} -at ${lig_ff} -nc ${charge_value} -pf y", + "parmchk2 -f prepc -i ${nmLigFile}.prepc -o ${nmLigFile}.frcmod\n", + "## Rename ligand pdb", + "antechamber -i ${nmLigFile}.prepc -fi prepc -o rename_${nmLigFile}.pdb -fo pdb\n" + ] + + assert amber_script == expected_output + + +def test_add_ligand_commands_spLig(mock_data): + """Test if add_ligand_commands correctly appends commands for a special ligand (spLig).""" + session = { + "spLig": True + } + uploadedFiles = { + "spLigFile": [("file6", "ligand.pdb")], + "prepcFile": [("file7", "ligand.prepc")], + "frcmodFile": [("file8", "ligand.frcmod")] + } + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + amber_script = [] + amber_script_gen.add_ligand_commands(amber_script) + + expected_output = [ + "################################## Ligand ######################################", + "# Special Ligand that is incompatible with GAFF force field", + "spLigFile=ligand # the file name of ligand without suffix `.pdb`", + "prepc=ligand # the file name without suffix `prepc`", + "frcmod=ligand # the file name without suffix `frcmod`\n", + "## Clean the PDB file by pdb4amber", + "pdb4amber -i ${spLigFile}.pdb -o ${spLigFile}_amber.pdb\n", + "spLigName=$(awk 'NR==1 {print $4}' ${spLigFile}_amber.pdb)\n" + ] + + assert amber_script == expected_output + +def test_add_combine_components_commands(mock_data): + """Test if add_combine_components_commands correctly appends commands to combine all components.""" + session, uploadedFiles = mock_data["session"], mock_data["uploadedFiles"] + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + amber_script = [] + amber_script_gen.add_combine_components_commands(amber_script) + + expected_output = [ + "###################### Combine All Components to Be Modelled ####################", + "cat > tleap.combine.in < tleap.combine.out", + "grep -v '^CONECT' comp.pdb > comp_cnt_rmv.pdb\n" + ] + + assert amber_script == expected_output + + +def test_add_solvation_commands(mock_data): + """Test if add_solvation_commands correctly appends commands for solvation settings.""" + session = { + "addType": "addWater", + "boxType": "cube", + "dist": "10.0" + } + uploadedFiles = {} + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + amber_script = [] + amber_script_gen.add_solvation_commands(amber_script) + + expected_output = [ + "boxType=solvatebox # `solvatebox`, a command in tleap, creates a cubic box ", + "dist=10.0 # the minimum distance between any atom originally present in solute and the edge of the periodic box." + ] + + assert amber_script == expected_output + + +@pytest.fixture +def mock_data_membrane(): + """Fixture to provide mock session and uploadedFiles data for membrane commands.""" + return { + "session": { + "addType": "addMembrane", + "lipid_tp": "custom_lipid", + "other_lipid_tp_input": "custom_lipid", + "lipid_ratio": "1", + "lipid_ff": "custom_lipid_ff", + "other_lipid_ff_input": "custom_lipid_ff", + "dist2Border": "10.0", + "padDist": "5.0" + }, + "uploadedFiles": {} + } + +def test_add_membrane_commands(mock_data_membrane): + """Test if add_membrane_commands correctly appends commands for membrane settings.""" + session, uploadedFiles = mock_data_membrane["session"], mock_data_membrane["uploadedFiles"] + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + amber_script = [] + amber_script_gen.add_membrane_commands(amber_script) + + # Define the expected output lines + expected_output = [ + "lipid_tp=custom_lipid", + "lipid_ratio=1", + "lipid_ff=custom_lipid_ff", + "dist2Border=10.0 # The minimum distance between the maxmin values for x y and z to the box boundaries. Flag --dist", + "padDist=5.0 # The width of the water layer over the membrane or protein in the z axis. Flag --dist_wat" + ] + + # Print the actual amber_script for debugging + print("Actual amber_script content:") + for line in amber_script: + print(line) + + # Check that the length of the amber_script matches the expected output + assert len(amber_script) == len(expected_output), "The number of lines in amber_script does not match the expected output." + + # Verify each line in amber_script matches the expected output + for i, (actual, expected) in enumerate(zip(amber_script, expected_output)): + assert actual == expected, f"Line {i} does not match: {actual}" + +def test_add_water_ff_commands(): + """Test if add_water_ff_commands correctly appends commands for water force field settings.""" + # Define a mock session and uploadedFiles + session = { + "water_ff": "tip3p", # Change this value to test different water force fields + "addType": "addWater", + # "other_water_ff_input" can be defined if water_ff is "other_water_ff" + } + uploadedFiles = {} # No uploaded files needed for this test + + # Instantiate the generator + amber_script_gen = AmberScriptGenerator(session, uploadedFiles) + + # Initialize the amber_script list + amber_script = [] + amber_script_gen.add_water_ff_commands(amber_script) + + # Define the expected output lines + expected_output = [ + "water_ff=tip3p", + "solvent=TIP3PBOX # set the water box" + ] + + # Print the actual amber_script for debugging + print("Actual amber_script content:") + for line in amber_script: + print(line) + + # Check that the length of the amber_script matches the expected output + assert len(amber_script) == len(expected_output), "The number of lines in amber_script does not match the expected output." + + # Verify each line in amber_script matches the expected output + for i, (actual, expected) in enumerate(zip(amber_script, expected_output)): + assert actual == expected, f"Line {i} does not match: {actual}" + +def test_add_ion_commands(): + """Test if add_ion_commands correctly appends commands for ion settings.""" + # Define mock sessions for different scenarios + sessions = [ + { + "pos_ion": "Na+", + "neg_ion": "Cl-", + "addType": "addWater", + "other_pos_ion_input": "custom_pos_ion", + "other_neg_ion_input": "custom_neg_ion", + "ionConc": "0.15" + }, + { + "pos_ion": "other_pos_ion", + "neg_ion": "other_neg_ion", + "other_pos_ion_input": "custom_pos_ion", + "other_neg_ion_input": "custom_neg_ion", + "addType": "addMembrane", + "ionConc": "0.15" + } + ] + + expected_outputs = [ + [ + "pos_ion=Na+", + "neg_ion=Cl-", + "numIon=0 # `numIon` is the flag for `addions` in tleap. When set to 0, the system will be neutralized", + "\n" + ], + [ + "pos_ion=custom_pos_ion # In development!", + "neg_ion=custom_neg_ion # In development!", + "ionConc=0.15", + "\n" + ] + ] + + for session, expected_output in zip(sessions, expected_outputs): + # Instantiate the generator + amber_script_gen = AmberScriptGenerator(session, {}) + + # Initialize the amber_script list + amber_script = [] + amber_script_gen.add_ion_commands(amber_script) + + # Print the actual amber_script for debugging + print("Actual amber_script content:") + for line in amber_script: + print(line) + + # Check that the length of the amber_script matches the expected output + assert len(amber_script) == len(expected_output), "The number of lines in amber_script does not match the expected output." + + # Verify each line in amber_script matches the expected output + for i, (actual, expected) in enumerate(zip(amber_script, expected_output)): + assert actual == expected, f"Line {i} does not match: {actual}" + +def test_add_membrane_building_commands(): + """Test if add_membrane_building_commands correctly appends commands for membrane settings.""" + + # Define mock sessions for different scenarios + sessions = [ + { + "addType": "addMembrane", + "nmLig": False, + "spLig": False, + "lipid_tp": "custom_lipid", + "lipid_ratio": "1", + "dist2Border": "10.0", + "padDist": "5.0", + "pos_ion": "Na+", + "ionConc": "0.15" + }, + { + "addType": "addMembrane", + "nmLig": True, + "spLig": False, + "lipid_tp": "custom_lipid", + "lipid_ratio": "1", + "dist2Border": "10.0", + "padDist": "5.0", + "pos_ion": "Na+", + "ionConc": "0.15" + }, + { + "addType": "addMembrane", + "nmLig": False, + "spLig": True, + "lipid_tp": "custom_lipid", + "lipid_ratio": "1", + "dist2Border": "10.0", + "padDist": "5.0", + "pos_ion": "Na+", + "ionConc": "0.15" + } + ] + + expected_outputs = [ + [ + "## Build the membrane", + "packmol-memgen --pdb ${rcp_nm}_cnt_rmv.pdb --lipids ${lipid_tp} --ratio ${lipid_ratio} --preoriented --dist ${dist2Border} --dist_wat ${padDist} --salt --salt_c ${pos_ion} --saltcon ${ionConc} --nottrim --overwrite --notprotonate\n", + "## Clean the complex pdb by `pdb4amber` for further `tleap` process", + "pdb4amber -i bilayer_${rcp_nm}_cnt_rmv.pdb -o clean_bilayer_${rcp_nm}.pdb", + "grep -v '^CONECT' clean_bilayer_${rcp_nm}.pdb > clean_bilayer_${rcp_nm}_cnt_rmv.pdb", + "\n" + ], + [ + "## Build the membrane", + "packmol-memgen --pdb comp.pdb --lipids ${lipid_tp} --ratio ${lipid_ratio} --preoriented --dist ${dist2Border} --dist_wat ${padDist} --salt --salt_c ${pos_ion} --saltcon ${ionConc} --nottrim --overwrite --notprotonate\n", + "## Clean the complex pdb by `pdb4amber` for further `tleap` process", + "pdb4amber -i bilayer_comp.pdb -o clean_bilayer_comp.pdb", + "grep -v '^CONECT' clean_bilayer_comp.pdb > clean_bilayer_comp_cnt_rmv.pdb", + "\n" + ], + [ + "## Build the membrane", + "packmol-memgen --pdb comp.pdb --lipids ${lipid_tp} --ratio ${lipid_ratio} --preoriented --dist ${dist2Border} --dist_wat ${padDist} --salt --salt_c ${pos_ion} --saltcon ${ionConc} --nottrim --overwrite --notprotonate\n", + "## Clean the complex pdb by `pdb4amber` for further `tleap` process", + "pdb4amber -i bilayer_comp.pdb -o clean_bilayer_comp.pdb", + "grep -v '^CONECT' clean_bilayer_comp.pdb > clean_bilayer_comp_cnt_rmv.pdb", + "\n" + ] + ] + + for session, expected_output in zip(sessions, expected_outputs): + # Instantiate the generator + amber_script_gen = AmberScriptGenerator(session, {}) + + # Initialize the amber_script list + amber_script = [] + amber_script_gen.add_membrane_building_commands(amber_script) + + # Print the actual amber_script for debugging + print("Actual amber_script content:") + for line in amber_script: + print(line) + + # Check that the length of the amber_script matches the expected output + assert len(amber_script) == len(expected_output), "The number of lines in amber_script does not match the expected output." + + # Verify each line in amber_script matches the expected output + for i, (actual, expected) in enumerate(zip(amber_script, expected_output)): + assert actual == expected, f"Line {i} does not match: {actual}" diff --git a/openmmdl/tests/openmmdl_setup/test_configfile_creator.py b/openmmdl/tests/openmmdl_setup/test_configfile_creator.py new file mode 100644 index 00000000..4c348b1c --- /dev/null +++ b/openmmdl/tests/openmmdl_setup/test_configfile_creator.py @@ -0,0 +1,565 @@ +import pytest +from openmmdl.openmmdl_setup.configfile_creator import ConfigCreator, ConfigWriter # Adjust import as necessary + +def test_add_openmmdl_ascii_art_logo(): + """Test if add_openmmdl_ascii_art_logo correctly adds the ASCII art logo.""" + + # Create a ConfigCreator instance + session = {} + uploadedFiles = {} + config_creator = ConfigCreator(session, uploadedFiles) + + # Initialize the script list + script = [] + config_creator.add_openmmdl_ascii_art_logo(script) + + # Expected ASCII art + expected_logo = """ + ,-----. .-------. .-''-. ,---. .--.,---. ,---.,---. ,---. ______ .---. + .' .-, '. \ _(`)_ \ .'_ _ \ | \ | || \ / || \ / || _ `''. | ,_| + / ,-.| \ _ \ | (_ o._)| / ( ` ) '| , \ | || , \/ , || , \/ , || _ | ) _ \,-./ ) + ; \ '_ / | :| (_,_) /. (_ o _) || |\_ \| || |\_ /| || |\_ /| ||( ''_' ) |\ '_ '`) + | _`,/ \ _/ || '-.-' | (_,_)___|| _( )_\ || _( )_/ | || _( )_/ | || . (_) `. | > (_) ) + : ( '\_/ \ ;| | ' \ .---.| (_ o _) || (_ o _) | || (_ o _) | ||(_ ._) '( . .-' + \ `"/ \ ) / | | \ `-' /| (_,_)\ || (_,_) | || (_,_) | || (_.\.' / `-'`-'|___ + '. \_/``".' / ) \ / | | | || | | || | | || .' | \ + '-----' `---' `'-..-' '--' '--''--' '--''--' '--''-----'` `--------` + """ + + # Compare script content with expected output + assert script[0].strip() == expected_logo.strip() + +def test_add_ascii_config_art(): + """Test if add_ascii_config_art correctly adds the configuration file ASCII art header.""" + + # Create a ConfigCreator instance + session = {} + uploadedFiles = {} + config_creator = ConfigCreator(session, uploadedFiles) + + # Initialize the script list + script = [] + config_creator.add_ascii_config_art(script) + + # Expected ASCII art + expected_art = """ + __ __ ___ __ ___ ___ + / ` / \ |\ | |__ | / _` |__ | | |__ + \__, \__/ | \| | | \__> | | |___ |___ + """ + + # Compare script content with expected output + assert script[0].strip() == expected_art.strip() + +@pytest.fixture +def config_creator_pdb(): + session = { + "fileType": "pdb", + "pdbType": "pdb", + "sdfFile": "ligand.sdf", + "ligandMinimization": "minimization_method", + "smallMoleculeForceField": "force_field", + "ligandSanitization": "sanitization_method", + "waterModel": "spce" + } + uploadedFiles = { + "file": [("protein.pdb", "path/to/protein.pdb")] + } + return ConfigCreator(session, uploadedFiles) + +def test_add_pdb_input_files_configuration(config_creator_pdb): + """Test if add_pdb_input_files_configuration correctly configures PDB input files.""" + + # Initialize the script list + script = [] + config_creator_pdb.add_pdb_input_files_configuration(script) + + # Expected script content + expected_lines = [ + "\n# Input Files", + "############# Ligand and Protein Data ###################", + "input_file_type = pdb", + "protein = path/to/protein.pdb", + "ligand = ligand.sdf", + "ligand_name = UNK", + "minimization = minimization_method", + "smallMoleculeForceField = force_field", + "sanitization = sanitization_method" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_amber(): + session = { + "fileType": "amber", + "has_files": "yes", + "nmLig": True, + "spLig": False, + "nmLigName": "nmLig", + "spLigName": None, + "water_ff": "tip3p" + } + uploadedFiles = { + "prmtopFile": [("prmtop.prmtop", "path/to/prmtop.prmtop")], + "inpcrdFile": [("inpcrd.inpcrd", "path/to/inpcrd.inpcrd")], + "nmLigFile": [("nmLig.sdf", "path/to/nmLig.sdf")] + } + return ConfigCreator(session, uploadedFiles) + +def test_add_amber_file_configuration(config_creator_amber): + """Test if add_amber_file_configuration correctly configures Amber files.""" + + # Initialize the script list + script = [] + config_creator_amber.add_amber_file_configuration(script) + + # Expected script content + expected_lines = [ + """####### Add the Amber Files in the Folder with this Script ####### \n""", + "input_file_type = amber", + "prmtop_file = path/to/prmtop.prmtop", + "inpcrd_file = path/to/inpcrd.inpcrd", + "prmtop = AmberPrmtopFile(prmtop_file)", + "inpcrd = AmberInpcrdFile(inpcrd_file)" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_ff(): + session = { + "fileType": "pdb", + "forcefield": "ff99SB", + "waterModel": "spce" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_forcefield_and_water_model_configuration(config_creator_ff): + """Test if add_forcefield_and_water_model_configuration correctly configures forcefield and water model.""" + + # Initialize the script list + script = [] + config_creator_ff.add_forcefield_and_water_model_configuration(script) + + # Expected script content + expected_lines = [ + "\n############# Forcefield, Water and Membrane Model Selection ###################\n", + "forcefield = ff99SB", + "water = spce" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_solvent(): + session = { + "fileType": "pdb", + "solvent": True, + "add_membrane": False, + "water_padding": True, + "water_padding_distance": "10.0", + "water_boxShape": "cubic", + "box_x": None, + "box_y": None, + "box_z": None, + "water_ionicstrength": "0.15", + "water_positive": "Na+", + "water_negative": "Cl-" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_solvent_configuration(config_creator_solvent): + """Test if add_solvent_configuration correctly configures solvent or membrane settings.""" + + # Initialize the script list + script = [] + config_creator_solvent.add_solvent_configuration(script) + + # Expected script content + expected_lines = [ + "\n############# Water Box Settings ###################\n", + "add_membrane = False", + "Water_Box = Buffer", + "water_padding_distance = 10.0", + "water_boxShape = cubic", + "water_ionicstrength = 0.15", + "water_positive_ion = Na+", + "water_negative_ion = Cl-" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_system(): + session = { + "nonbondedMethod": "PME", + "cutoff": "1.0", + "ewaldTol": "0.0005", + "hmr": True, + "hmrMass": "1.008", + "constraints": "hbonds", + "constraintTol": "0.0001" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_system_configuration(config_creator_system): + """Test if add_system_configuration correctly configures system settings.""" + + # Initialize the script list + script = [] + config_creator_system.add_system_configuration(script) + print(script) + + # Expected script content + expected_lines = [ + "\n# System Configuration\n", + "nonbondedMethod = app.PME", + "nonbondedCutoff = 1.0*unit.nanometers", + "ewaldErrorTolerance = 0.0005", + "hmrOptions = ', hydrogenMass=hydrogenMass'", + "constraints = app.HBonds", + "rigidWater = True", + "constraintTolerance = 0.0001", + "hydrogenMass = 1.008*unit.amu" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_integrator(): + session = { + "dt": "0.002", + "temperature": "300", + "friction": "1.0", + "ensemble": "npt", + "pressure": "1.0", + "barostatInterval": "100" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_integration_configuration(config_creator_integrator): + """Test if add_integration_configuration correctly configures integration settings.""" + + # Initialize the script list + script = [] + config_creator_integrator.add_integration_configuration(script) + + # Expected script content + expected_lines = [ + "\n# Integration Configuration\n", + "step_time = 0.002", + "dt = 0.002*unit.picoseconds", + "temperature = 300*unit.kelvin", + "friction = 1.0/unit.picosecond", + "pressure = 1.0*unit.atmospheres", + "barostatInterval = 100" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_sim_time(): + session = { + "sim_length": "1000", # Simulation length in ns + "dt": "0.002", # Time step in ps + "dcdFrames": "5000", # Number of frames for DCD output + "pdbInterval_ns": "10" # Interval for PDB output in ns + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_simulation_time_and_steps_configuration(config_creator_sim_time): + """Test if add_simulation_time_and_steps_configuration correctly configures simulation time and steps.""" + + # Initialize the script list + script = [] + config_creator_sim_time.add_simulation_time_and_steps_configuration(script) + + # Calculate expected values + steps = int(float("1000") / float("0.002") * 1000) # Total steps + dcdinterval = int(steps / int("5000")) # DCD interval + pdbInterval = int( + steps * (float("10") / float("1000")) # PDB interval + ) + + # Expected script content + expected_lines = [ + "\n# Simulation Time and Steps Configuration\n", + "sim_length = 1000", + "steps = %s" % steps, + "\n# Frames and Interval Configuration\n", + "dcdFrames = 5000", + "dcdInterval = %s" % dcdinterval, + "pdbInterval_ns = 10", + "pdbInterval = %s" % pdbInterval + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_equil(): + session = { + "equilibration": "equilibration" # Change to "minimization" for testing that case + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_equilibration_configuration(config_creator_equil): + """Test if add_equilibration_configuration correctly configures equilibration or minimization settings.""" + + # Initialize the script list + script = [] + config_creator_equil.add_equilibration_configuration(script) + + # Expected script content + expected_lines = [ + "\n# Equilibration & Minimization Configuration\n", + "preparation_type = equilibration" + ] + + # Compare script content with expected output + assert script == expected_lines + + # Change the session parameter to test "minimization" + config_creator_equil.session["equilibration"] = "minimization" + script = [] + config_creator_equil.add_equilibration_configuration(script) + + # Expected script content for minimization + expected_lines = [ + "\n# Equilibration & Minimization Configuration\n", + "preparation_type = minimization" + ] + + # Compare script content with expected output + assert script == expected_lines + +@pytest.fixture +def config_creator_simulation(): + session = { + "platform": "CUDA", + "precision": "mixed", + "writeDCD": True, + "dcdFilename": "simulation", + "writeData": True, + "dataFields": ["energy", "temperature"], + "dataInterval": "1000", + "restart_checkpoint": "yes", + "restart_step": "step1", + "dataFilename": "data_reporter" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_simulation_configuration(config_creator_simulation): + """Test if add_simulation_configuration correctly configures simulation platform, precision, and file outputs.""" + + # Initialize the script list + script = [] + config_creator_simulation.add_simulation_configuration(script) + + # Expected script content + expected_lines = [ + "\n# Simulation Configuration\n", + "platform = CUDA", + "platformProperties = {'Precision': 'mixed'}", + "dcd_name = step1_simulation", + "dataReporter = StateDataReporter('step1_data_reporter', 1000, totalSteps=steps,", + " energy=True, temperature=True, separator='\\t')" + ] + + # Compare script content with expected output + assert script == expected_lines + + # Test case with no data reporting + config_creator_simulation.session["writeData"] = False + script = [] + config_creator_simulation.add_simulation_configuration(script) + + # Expected script content without dataReporter + expected_lines = [ + "\n# Simulation Configuration\n", + "platform = CUDA", + "platformProperties = {'Precision': 'mixed'}", + "dcd_name = step1_simulation" + ] + + assert script == expected_lines + + +@pytest.fixture +def config_creator_checkpoint(): + session = { + "writeCheckpoint": True, + "checkpointInterval_ns": "500", # Checkpoint interval in ns + "dt": "0.002", # Time step in ps + "checkpointFilename": "checkpoint", + "restart_checkpoint": "yes", + "restart_step": "step1" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_checkpoint_configuration(config_creator_checkpoint): + """Test if add_checkpoint_configuration correctly configures checkpoint and restart settings.""" + + # Initialize the script list + script = [] + config_creator_checkpoint.add_checkpoint_configuration(script) + + # Calculate expected checkpoint interval + checkpointInterval = int( + 1000 * float("500") / float("0.002") + ) + + # Expected script content + expected_lines = [ + "\n# Checkpoint and Restart Configuration\n", + "checkpointInterval = %s" % checkpointInterval, + "checkpoint_name = checkpoint", + "restart_step = step1" + ] + + # Compare script content with expected output + assert script == expected_lines + + # Test case with no checkpoint + config_creator_checkpoint.session["writeCheckpoint"] = False + script = [] + config_creator_checkpoint.add_checkpoint_configuration(script) + + # Expected script content without checkpoint configuration + expected_lines = [] + + assert script == expected_lines + +@pytest.fixture +def config_creator_xml(): + session = { + "writeSimulationXml": True, + "systemXmlFilename": "system.xml", + "integratorXmlFilename": "integrator.xml" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_xml_serialization_configuration(config_creator_xml): + """Test if add_xml_serialization_configuration correctly configures XML serialization settings.""" + + # Initialize the script list + script = [] + config_creator_xml.add_xml_serialization_configuration(script) + + # Expected script content + expected_lines = [ + "\n# Write XML Serialized Objects\n", + "xmlsystem_filename = system.xml", + "xmlintegrator_filename = integrator.xml" + ] + + # Compare script content with expected output + assert script == expected_lines + + # Test case with no XML serialization + config_creator_xml.session["writeSimulationXml"] = False + script = [] + config_creator_xml.add_xml_serialization_configuration(script) + + # Expected script content without XML serialization configuration + expected_lines = [] + + assert script == expected_lines + +@pytest.fixture +def config_creator_postprocessing(): + session = { + "md_postprocessing": "enabled", + "mdtraj_output": "mdtraj_output.pdb", + "mdtraj_removal": "mdtraj_removal.pdb", + "mda_output": "mda_output.h5", + "mda_selection": "resname LIG" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_postprocessing_configuration(config_creator_postprocessing): + """Test if add_postprocessing_configuration correctly configures MD post-processing settings.""" + + # Initialize the script list + script = [] + config_creator_postprocessing.add_postprocessing_configuration(script) + + # Expected script content + expected_lines = [ + "\n# Post-Processing Configuration\n", + "postprocessing = enabled", + "old_output = mdtraj_output.pdb", + "old_removal = mdtraj_removal.pdb", + "mda_output = mda_output.h5", + "mda_selection = resname LIG" + ] + + # Compare script content with expected output + assert script == expected_lines + + +@pytest.fixture +def config_creator_analysis(): + session = { + "openmmdl_analysis": "Yes", + "analysis_selection": "all", + "binding_mode": "flexible", + "min_transition": "5.0", + "rmsd_diff": "0.1", + "pml_generation": "enabled" + } + uploadedFiles = {} + return ConfigCreator(session, uploadedFiles) + +def test_add_openmmdl_analysis_configuration(config_creator_analysis): + """Test if add_openmmdl_analysis_configuration correctly configures OpenMMDL Analysis settings.""" + + # Initialize the script list + script = [] + config_creator_analysis.add_openmmdl_analysis_configuration(script) + + # Expected script content + expected_lines = [ + "\n# OpenMMDL Analysis Configuration\n", + "openmmdl_analysis = Yes", + "analysis_selection = all", + "binding_mode = flexible", + "min_transition = 5.0", + "rmsd_diff = 0.1", + "pml_generation = enabled" + ] + + # Compare script content with expected output + assert script == expected_lines + + # Test case with OpenMMDL Analysis disabled + config_creator_analysis.session["openmmdl_analysis"] = "No" + script = [] + config_creator_analysis.add_openmmdl_analysis_configuration(script) + + # Expected script content without OpenMMDL Analysis configuration + expected_lines = [ + "\n# OpenMMDL Analysis Configuration\n", + "openmmdl_analysis = No" + ] + + assert script == expected_lines + + diff --git a/openmmdl/tests/openmmdl_setup/test_file_operator.py b/openmmdl/tests/openmmdl_setup/test_file_operator.py new file mode 100644 index 00000000..3a8b8425 --- /dev/null +++ b/openmmdl/tests/openmmdl_setup/test_file_operator.py @@ -0,0 +1,100 @@ +import pytest +from werkzeug.datastructures import FileStorage +from io import BytesIO +from openmmdl.openmmdl_setup.file_operator import LigandExtractor, FileUploader # Replace with the actual module name +import tempfile + +# Test cases for LigandExtractor +class TestLigandExtractor: + def test_extract_ligand_name_pdb(self): + lig_file_name = "ligand.pdb" + expected_output = "ligand" + assert LigandExtractor.extract_ligand_name(lig_file_name) == expected_output + + def test_extract_ligand_name_sdf(self): + lig_file_name = "ligand.sdf" + expected_output = "UNL" + assert LigandExtractor.extract_ligand_name(lig_file_name) == expected_output + + def test_extract_ligand_name_invalid_extension(self): + lig_file_name = "ligand.txt" + with pytest.raises(ValueError, match="Unsupported file format. Only .sdf and .pdb are supported."): + LigandExtractor.extract_ligand_name(lig_file_name) + + def test_extract_ligand_name_non_string(self): + lig_file_name = 12345 + with pytest.raises(TypeError, match="lig_file_name must be a string"): + LigandExtractor.extract_ligand_name(lig_file_name) + + + +# Custom class to mock the behavior of request.files in Flask +class MockFileMultiDict: + """ + This class mimics the behavior of Flask's request.files MultiDict for testing purposes. + """ + def __init__(self, file_dict): + self.file_dict = file_dict + + def getlist(self, key): + return self.file_dict.get(key, []) + + def __iter__(self): + return iter(self.file_dict) + + def items(self): + return self.file_dict.items() + +# Test cases for FileUploader +class TestFileUploader: + @pytest.fixture + def fake_request(self): + """ + Simulates a fake request with files using FileStorage and provides the getlist() method. + """ + file1 = FileStorage( + stream=BytesIO(b"dummy content 1"), filename="file1.txt", content_type="text/plain" + ) + file2 = FileStorage( + stream=BytesIO(b"dummy content 2"), filename="file2.txt", content_type="text/plain" + ) + + class FakeRequest: + def __init__(self): + # Mimic request.files using the MockFileMultiDict + self.files = MockFileMultiDict({ + "file_field_1": [file1], + "file_field_2": [file2], + }) + + return FakeRequest() + + def test_save_uploaded_files_success(self, fake_request): + uploadedFiles = {} + FileUploader.save_uploaded_files(uploadedFiles, fake_request) + + # Verify that files were saved correctly + assert "file_field_1" in uploadedFiles + assert "file_field_2" in uploadedFiles + assert len(uploadedFiles["file_field_1"]) == 1 + assert uploadedFiles["file_field_1"][0][1] == "file1.txt" + assert len(uploadedFiles["file_field_2"]) == 1 + assert uploadedFiles["file_field_2"][0][1] == "file2.txt" + + def test_save_uploaded_files_invalid_dict(self, fake_request): + uploadedFiles = [] + with pytest.raises(TypeError, match="uploadedFiles must be a dictionary"): + FileUploader.save_uploaded_files(uploadedFiles, fake_request) + + def test_save_uploaded_files_invalid_request(self): + uploadedFiles = {} + invalid_request = object() # Request without 'files' attribute + with pytest.raises(TypeError, match="request object must have a 'files' attribute"): + FileUploader.save_uploaded_files(uploadedFiles, invalid_request) + + def test_save_uploaded_files_non_filestorage(self, fake_request): + # Modify fake_request to include an invalid file type (non-FileStorage instance) + fake_request.files.file_dict["file_field_1"] = [object()] # Invalid file type + uploadedFiles = {} + with pytest.raises(TypeError, match="file must be a FileStorage instance"): + FileUploader.save_uploaded_files(uploadedFiles, fake_request) diff --git a/openmmdl/tests/openmmdl_setup/test_setup_options.py b/openmmdl/tests/openmmdl_setup/test_setup_options.py new file mode 100644 index 00000000..7202921c --- /dev/null +++ b/openmmdl/tests/openmmdl_setup/test_setup_options.py @@ -0,0 +1,198 @@ +import pytest +from typing import List, Dict +from flask import session +from werkzeug.datastructures import ImmutableMultiDict +from openmmdl.openmmdl_setup.setup_options import SetupOptionsConfigurator, SessionDict, RequestSessionManager + +@pytest.fixture +def default_session() -> SessionDict: + """ + Provides a default mock session with minimal required data for testing. + """ + return { + "fileType": "pdb", + "waterModel": "explicit", + } + +@pytest.fixture +def configurator(default_session: SessionDict) -> SetupOptionsConfigurator: + """ + Provides a SetupOptionsConfigurator instance initialized with the default session. + """ + return SetupOptionsConfigurator(session=default_session) + +def test_configure_default_options(configurator: SetupOptionsConfigurator, default_session: SessionDict): + """ + Test the `configure_default_options` method to ensure default values are properly set. + """ + configurator.configure_default_options() + + assert default_session["restart_checkpoint"] is False + assert default_session["mdtraj_output"] == "mdtraj_pdb_dcd" + assert default_session["mda_output"] == "mda_pdb_dcd" + assert default_session["analysis_selection"] == "analysis_all" + assert default_session["binding_mode"] == "40" + assert default_session["ensemble"] == "npt" # since waterModel is 'explicit' + assert default_session["platform"] == "CUDA" + assert default_session["precision"] == "mixed" + assert default_session["cutoff"] == "1.0" # as waterModel is not 'implicit' + assert default_session["hmr"] is True + assert default_session["writeDCD"] is True + assert default_session["dataFields"] == ["step", "speed", "progress", "potentialEnergy", "temperature"] + assert default_session["writeCheckpoint"] is True + +def test_configure_default_options_with_implicit_water(configurator: SetupOptionsConfigurator, default_session: SessionDict): + """ + Test `configure_default_options` when the water model is set to implicit. + """ + default_session["waterModel"] = "implicit" + configurator.configure_default_options() + + assert default_session["ensemble"] == "nvt" # should switch to nvt due to implicit water + assert default_session["cutoff"] == "2.0" # cutoff should change + assert default_session["nonbondedMethod"] == "CutoffNonPeriodic" + +def test_configure_default_amber_options(configurator: SetupOptionsConfigurator, default_session: SessionDict): + """ + Test the `configureDefaultAmberOptions` method to ensure Amber options are set correctly. + """ + configurator.configureDefaultAmberOptions() + + assert default_session["lig_ff"] == "gaff2" + assert default_session["charge_value"] == "0" + assert default_session["charge_method"] == "bcc" + assert default_session["prot_ff"] == "ff19SB" + assert default_session["dna_ff"] == "OL15" + assert default_session["rna_ff"] == "OL3" + assert default_session["carbo_ff"] == "GLYCAM_06j" + assert default_session["addType"] == "addWater" + assert default_session["boxType"] == "cube" + assert default_session["lipid_tp"] == "POPC" + assert default_session["dist2Border"] == "15" + assert default_session["water_ff"] == "opc" + assert default_session["pos_ion"] == "Na+" + assert default_session["neg_ion"] == "Cl-" + assert default_session["ionConc"] == "0.15" + + +@pytest.fixture +def app_context(): + """ + Provides a Flask test request context for session management. + """ + from flask import Flask + + app = Flask(__name__) + app.secret_key = 'test_secret_key' # Required to use sessions + with app.test_request_context(): + yield + +@pytest.fixture +def default_form() -> ImmutableMultiDict: + """ + Provides default mock form data for testing. + """ + return ImmutableMultiDict({ + "rcpType": "protein", + "prot_ff": "ff14SB", + "other_prot_ff_input": "custom_prot_ff", + "dna_ff": "OL15", + "other_dna_ff_input": "custom_dna_ff", + "rna_ff": "OL3", + "other_rna_ff_input": "custom_rna_ff", + "carbo_ff": "GLYCAM_06j", + "addType": "addWater", + "boxType": "geometry", + "geomPadding": "10", + "ionicstrength": "0.15", + "positiveion": "Na+", + "negativeion": "Cl-", + "forcefield": "amber14", + "ml_forcefield": "openff", + "waterModel": "tip3p", + "smallMoleculeForceField": "gaff", + "ligandMinimization": "Yes", + "ligandSanitization": "Yes", + "writeDCD": "True", + "writeData": "True", + "writeCheckpoint": "True", + "dataFields": "step,speed,temperature", + "hmr": "True", + }) + +@pytest.fixture +def request_manager(default_form) -> RequestSessionManager: + """ + Provides a RequestSessionManager instance initialized with the default form data. + """ + return RequestSessionManager(form=default_form) + +def test_set_amber_options_rcp_session(request_manager: RequestSessionManager, app_context): + """ + Test the `setAmberOptions_rcp_session` method. + """ + request_manager.setAmberOptions_rcp_session() + + assert session["rcpType"] == "protein" + assert session["prot_ff"] == "ff14SB" + assert session["other_prot_ff_input"] == "custom_prot_ff" + assert session["dna_ff"] == "OL15" + assert session["other_dna_ff_input"] == "custom_dna_ff" + assert session["rna_ff"] == "OL3" + assert session["other_rna_ff_input"] == "custom_rna_ff" + assert session["carbo_ff"] == "GLYCAM_06j" + +def test_set_amber_options_water_membrane_session(request_manager: RequestSessionManager, app_context): + """ + Test the `setAmberOptions_water_membrane_session` method. + """ + request_manager.setAmberOptions_water_membrane_session() + + assert session["addType"] == "addWater" + assert session["boxType"] == "geometry" + assert session["dist"] == "" + assert session["lipid_tp"] == "" + assert session["other_lipid_tp_input"] == "" + assert session["lipid_ratio"] == "" + assert session["lipid_ff"] == "" + assert session["dist2Border"] == "" + assert session["padDist"] == "" + +def test_simulationoptions_add_general_settings(request_manager: RequestSessionManager, app_context): + """ + Test the `simulationoptions_add_general_settings` method to ensure general simulation settings are correctly added to the session. + """ + request_manager.simulationoptions_add_general_settings() + + assert session["forcefield"] == "amber14" + assert session["ml_forcefield"] == "openff" + assert session["waterModel"] == "tip3p" + assert session["smallMoleculeForceField"] == "gaff" + assert session["ligandMinimization"] == "Yes" + assert session["ligandSanitization"] == "Yes" + assert session["writeDCD"] is True + assert session["writeData"] is True + assert session["writeCheckpoint"] is True + assert session["dataFields"] == ["step,speed,temperature"] + assert session["hmr"] is True + +def test_configure_files_add_forcefield_ligand_settings(request_manager: RequestSessionManager, app_context): + """ + Test the `configureFiles_add_forcefield_ligand_settings` method to ensure forcefield and ligand settings are added to the session. + """ + request_manager.configureFiles_add_forcefield_ligand_settings() + + assert session["forcefield"] == "amber14" + assert session["ml_forcefield"] == "openff" + assert session["waterModel"] == "tip3p" + assert session["smallMoleculeForceField"] == "gaff" + assert session["ligandMinimization"] == "Yes" + assert session["ligandSanitization"] == "Yes" + +def test_parse_float(request_manager: RequestSessionManager): + """ + Test the `_parse_float` helper function. + """ + assert request_manager._parse_float("10.5") == 10.5 + assert request_manager._parse_float(None) is None + assert request_manager._parse_float("invalid") is None