@@ -950,3 +950,203 @@ def test_filter_functions_non_overlapping_tests_root():
950950
951951 # Strict check: exactly 2 functions remaining
952952 assert count == 2 , f"Expected exactly 2 functions, got { count } "
953+
954+
955+ def test_filter_functions_project_inside_tests_folder ():
956+ """Test that source files are not filtered when project is inside a folder named 'tests'.
957+
958+ This is a critical regression test for projects located at paths like:
959+ - /home/user/tests/myproject/
960+ - /Users/dev/tests/n8n/
961+
962+ The fix ensures that directory pattern matching (e.g., /tests/) is only checked
963+ on the relative path from project_root, not on the full absolute path.
964+ """
965+ with tempfile .TemporaryDirectory () as outer_temp_dir_str :
966+ outer_temp_dir = Path (outer_temp_dir_str )
967+
968+ # Create a "tests" folder to simulate /home/user/tests/
969+ tests_parent_folder = outer_temp_dir / "tests"
970+ tests_parent_folder .mkdir ()
971+
972+ # Create project inside the "tests" folder - simulates /home/user/tests/myproject/
973+ project_dir = tests_parent_folder / "myproject"
974+ project_dir .mkdir ()
975+
976+ # Create source file inside the project
977+ src_dir = project_dir / "src"
978+ src_dir .mkdir ()
979+ source_file = src_dir / "utils.py"
980+ with source_file .open ("w" ) as f :
981+ f .write ("""
982+ def deep_copy(obj):
983+ \" \" \" Deep copy an object.\" \" \"
984+ import copy
985+ return copy.deepcopy(obj)
986+
987+ def compare_values(a, b):
988+ \" \" \" Compare two values.\" \" \"
989+ return a == b
990+ """ )
991+
992+ # Create another source file directly in project root
993+ root_source_file = project_dir / "main.py"
994+ with root_source_file .open ("w" ) as f :
995+ f .write ("""
996+ def main():
997+ \" \" \" Main entry point.\" \" \"
998+ return 0
999+ """ )
1000+
1001+ # Create actual test files that should be filtered
1002+ project_tests_dir = project_dir / "test"
1003+ project_tests_dir .mkdir ()
1004+ test_file = project_tests_dir / "test_utils.py"
1005+ with test_file .open ("w" ) as f :
1006+ f .write ("""
1007+ def test_deep_copy():
1008+ return True
1009+ """ )
1010+
1011+ # Discover functions
1012+ all_functions = {}
1013+ for file_path in [source_file , root_source_file , test_file ]:
1014+ discovered = find_all_functions_in_file (file_path )
1015+ all_functions .update (discovered )
1016+
1017+ # Test: project at /outer/tests/myproject with tests_root overlapping
1018+ # This simulates: /home/user/tests/n8n with tests_root = /home/user/tests/n8n
1019+ with unittest .mock .patch (
1020+ "codeflash.discovery.functions_to_optimize.get_blocklisted_functions" , return_value = {}
1021+ ):
1022+ filtered , count = filter_functions (
1023+ all_functions ,
1024+ tests_root = project_dir , # Same as project_root (overlapping)
1025+ ignore_paths = [],
1026+ project_root = project_dir , # /outer/tests/myproject
1027+ module_root = project_dir ,
1028+ )
1029+
1030+ # Strict check: source files should NOT be filtered even though
1031+ # the full path contains "/tests/" in the parent directory
1032+ expected_files = {source_file , root_source_file }
1033+ actual_files = set (filtered .keys ())
1034+
1035+ assert actual_files == expected_files , (
1036+ f"Source files were incorrectly filtered when project is inside 'tests' folder.\n "
1037+ f"Expected files: { expected_files } \n "
1038+ f"Got files: { actual_files } \n "
1039+ f"Project path: { project_dir } \n "
1040+ f"This indicates the /tests/ pattern matched the parent directory path."
1041+ )
1042+
1043+ # Verify the correct functions are present
1044+ source_functions = sorted ([fn .function_name for fn in filtered .get (source_file , [])])
1045+ assert source_functions == ["compare_values" , "deep_copy" ], (
1046+ f"Expected ['compare_values', 'deep_copy'], got { source_functions } "
1047+ )
1048+
1049+ root_functions = [fn .function_name for fn in filtered .get (root_source_file , [])]
1050+ assert root_functions == ["main" ], (
1051+ f"Expected ['main'], got { root_functions } "
1052+ )
1053+
1054+ # Strict check: exactly 3 functions (2 from utils.py + 1 from main.py)
1055+ assert count == 3 , (
1056+ f"Expected exactly 3 functions, got { count } . "
1057+ f"Some source files may have been incorrectly filtered."
1058+ )
1059+
1060+ # Verify test file was properly filtered (should not be in results)
1061+ assert test_file not in filtered , (
1062+ f"Test file { test_file } should have been filtered but wasn't"
1063+ )
1064+
1065+
1066+ def test_filter_functions_typescript_project_in_tests_folder ():
1067+ """Test TypeScript-like project structure inside a folder named 'tests'.
1068+
1069+ This simulates the n8n project structure:
1070+ /home/user/tests/n8n/packages/workflow/src/utils.ts
1071+
1072+ Ensures that TypeScript source files are not incorrectly filtered
1073+ when the parent directory happens to be named 'tests'.
1074+ """
1075+ with tempfile .TemporaryDirectory () as outer_temp_dir_str :
1076+ outer_temp_dir = Path (outer_temp_dir_str )
1077+
1078+ # Simulate: /home/user/tests/n8n
1079+ tests_folder = outer_temp_dir / "tests"
1080+ tests_folder .mkdir ()
1081+ n8n_project = tests_folder / "n8n"
1082+ n8n_project .mkdir ()
1083+
1084+ # Simulate: packages/workflow/src/utils.py (using .py for testing)
1085+ packages_dir = n8n_project / "packages"
1086+ packages_dir .mkdir ()
1087+ workflow_dir = packages_dir / "workflow"
1088+ workflow_dir .mkdir ()
1089+ src_dir = workflow_dir / "src"
1090+ src_dir .mkdir ()
1091+
1092+ # Source file deep in the monorepo structure
1093+ utils_file = src_dir / "utils.py"
1094+ with utils_file .open ("w" ) as f :
1095+ f .write ("""
1096+ def deep_copy(source):
1097+ \" \" \" Create a deep copy of the source object.\" \" \"
1098+ if source is None:
1099+ return None
1100+ return source.copy() if hasattr(source, 'copy') else source
1101+
1102+ def is_object_empty(obj):
1103+ \" \" \" Check if an object is empty.\" \" \"
1104+ return len(obj) == 0 if obj else True
1105+ """ )
1106+
1107+ # Create test directory inside the package (simulating packages/workflow/test/)
1108+ test_dir = workflow_dir / "test"
1109+ test_dir .mkdir ()
1110+ test_file = test_dir / "utils.test.py"
1111+ with test_file .open ("w" ) as f :
1112+ f .write ("""
1113+ def test_deep_copy():
1114+ return True
1115+
1116+ def test_is_object_empty():
1117+ return True
1118+ """ )
1119+
1120+ # Discover functions
1121+ all_functions = {}
1122+ for file_path in [utils_file , test_file ]:
1123+ discovered = find_all_functions_in_file (file_path )
1124+ all_functions .update (discovered )
1125+
1126+ # Test with module_root = packages (typical TypeScript monorepo setup)
1127+ with unittest .mock .patch (
1128+ "codeflash.discovery.functions_to_optimize.get_blocklisted_functions" , return_value = {}
1129+ ):
1130+ filtered , count = filter_functions (
1131+ all_functions ,
1132+ tests_root = packages_dir , # Overlapping with module_root
1133+ ignore_paths = [],
1134+ project_root = n8n_project , # /outer/tests/n8n
1135+ module_root = packages_dir , # /outer/tests/n8n/packages
1136+ )
1137+
1138+ # Strict check: only the source file should remain
1139+ assert set (filtered .keys ()) == {utils_file }, (
1140+ f"Expected only { utils_file } but got { set (filtered .keys ())} .\n "
1141+ f"Source files in /outer/tests/n8n/packages/workflow/src/ were incorrectly filtered.\n "
1142+ f"The /tests/ pattern in the parent path should not affect filtering."
1143+ )
1144+
1145+ # Verify the correct functions are present
1146+ filtered_functions = sorted ([fn .function_name for fn in filtered .get (utils_file , [])])
1147+ assert filtered_functions == ["deep_copy" , "is_object_empty" ], (
1148+ f"Expected ['deep_copy', 'is_object_empty'], got { filtered_functions } "
1149+ )
1150+
1151+ # Strict check: exactly 2 functions
1152+ assert count == 2 , f"Expected exactly 2 functions, got { count } "
0 commit comments