import contextlib import io from pathlib import Path import unittest from moliety2.features import ( FEATURE_MODES, FOCUSED_ATOM_HIGHLIGHT_COLOR, depict_input_smiles, feature_mode_source, process_smiles_main, ) from moliety2.patterns import daylight_smarts_patterns, smartsrx_patterns, uni_pka_patterns class ProcessSmilesMainTests(unittest.TestCase): def test_input_depiction_is_separate_from_feature_outputs(self): input_image = depict_input_smiles("CCO") output_images, _message = process_smiles_main("CCO", "Functional Groups") self.assertIsInstance(input_image, str) self.assertNotIn(input_image, [image for image, _caption in output_images]) def test_invalid_smiles_returns_empty_gallery(self): images, message = process_smiles_main("not-a-smiles", "Functional Groups") self.assertEqual(images, []) self.assertEqual(message, "Invalid SMILES.") def test_invalid_smiles_returns_no_input_depiction(self): self.assertIsNone(depict_input_smiles("not-a-smiles")) def test_invalid_mode_returns_empty_gallery(self): images, message = process_smiles_main("CCO", "Unknown Mode") self.assertEqual(images, []) self.assertEqual(message, "Invalid mode selected.") def test_all_modes_return_lists(self): smiles = "CC(=O)Oc1ccccc1C(=O)O" for mode in FEATURE_MODES: with self.subTest(mode=mode.name): images, message = process_smiles_main(smiles, mode.name) self.assertIsInstance(images, list) self.assertIsInstance(message, str) def test_all_modes_have_display_sources(self): for mode in FEATURE_MODES: with self.subTest(mode=mode.name): self.assertTrue(mode.source.strip()) self.assertEqual(feature_mode_source(mode.name), f"Source: {mode.source}") def test_no_result_modes_return_empty_lists_not_none(self): for mode_name in ("Chiral Centers", "Potential Stereogenic Centers"): with self.subTest(mode=mode_name): images, message = process_smiles_main("CCO", mode_name) self.assertEqual(images, []) self.assertIn("No ", message) def test_protonation_rejects_inverted_ph_range(self): images, message = process_smiles_main("CCO", "Protonation", 8.0, 7.0) self.assertEqual(images, []) self.assertEqual(message, "Minimum pH must be less than or equal to maximum pH.") class PatternLoadingTests(unittest.TestCase): def test_daylight_smarts_loading_skips_invalid_patterns_quietly(self): daylight_smarts_patterns.cache_clear() stderr = io.StringIO() with contextlib.redirect_stderr(stderr): patterns = daylight_smarts_patterns() self.assertGreater(len(patterns), 0) self.assertNotIn("SMARTS Parse Error", stderr.getvalue()) def test_smartsrx_loading_skips_invalid_patterns_quietly(self): smartsrx_patterns.cache_clear() stderr = io.StringIO() with contextlib.redirect_stderr(stderr): patterns = smartsrx_patterns() self.assertGreater(len(patterns), 0) self.assertIn("Acid > Acid_Aliphatic > Acid_SaturatedAliphatic", patterns) self.assertNotIn("SMARTS Parse Error", stderr.getvalue()) def test_smartsrx_mode_finds_carboxylic_acid(self): images, message = process_smiles_main("CC(=O)O", "SMARTS-RX Moieties") self.assertGreater(len(images), 0) self.assertIn("Found ", message) def test_uni_pka_loading_skips_invalid_patterns_quietly(self): uni_pka_patterns.cache_clear() stderr = io.StringIO() with contextlib.redirect_stderr(stderr): patterns = uni_pka_patterns() self.assertGreater(len(patterns), 0) self.assertTrue(any(pattern.name.startswith("Carboxyl acid") for pattern in patterns)) self.assertNotIn("SMARTS Parse Error", stderr.getvalue()) def test_uni_pka_mode_finds_carboxylic_acid(self): images, message = process_smiles_main("CC(=O)O", "Uni-pKa Ionizable Sites") self.assertGreater(len(images), 0) self.assertIn("Found ", message) def test_uni_pka_mode_renders_without_explicit_hydrogens(self): images, _message = process_smiles_main("CC(=O)O", "Uni-pKa Ionizable Sites") svg_text = Path(images[0][0]).read_text(encoding="utf-8") self.assertNotIn(">H<", svg_text) def test_uni_pka_mode_renders_focused_atom_color(self): images, _message = process_smiles_main("CC(=O)O", "Uni-pKa Ionizable Sites") svg_text = Path(images[0][0]).read_text(encoding="utf-8") red, green, blue = (int(channel * 255) for channel in FOCUSED_ATOM_HIGHLIGHT_COLOR) self.assertIn(f"#{red:02X}{green:02X}{blue:02X}", svg_text) if __name__ == "__main__": unittest.main()