@@ -87,3 +87,124 @@ fn scan_fixtures() {
8787
8888 // Note: legacy negative fixtures removed; comprehensive fixtures are used now.
8989}
90+
91+ #[ test]
92+ fn scan_nested_general_fixtures ( ) {
93+ let workspace = PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "../.." ) ;
94+ let patterns_path = workspace. join ( "patterns.toml" ) ;
95+ let patterns = std:: fs:: read_to_string ( patterns_path) . unwrap ( ) ;
96+ let reg = PatternRegistry :: load ( & patterns) . unwrap ( ) ;
97+ let reg = Arc :: new ( reg) ;
98+ let dets: Vec < Box < dyn Detector > > = vec ! [
99+ Box :: new( PatternDetector :: new(
100+ "detector-go" ,
101+ & [ Language :: Go ] ,
102+ reg. clone( ) ,
103+ ) ) ,
104+ Box :: new( PatternDetector :: new(
105+ "detector-java" ,
106+ & [ Language :: Java ] ,
107+ reg. clone( ) ,
108+ ) ) ,
109+ Box :: new( PatternDetector :: new(
110+ "detector-c" ,
111+ & [ Language :: C ] ,
112+ reg. clone( ) ,
113+ ) ) ,
114+ Box :: new( PatternDetector :: new(
115+ "detector-cpp" ,
116+ & [ Language :: Cpp ] ,
117+ reg. clone( ) ,
118+ ) ) ,
119+ Box :: new( PatternDetector :: new(
120+ "detector-rust" ,
121+ & [ Language :: Rust ] ,
122+ reg. clone( ) ,
123+ ) ) ,
124+ Box :: new( PatternDetector :: new(
125+ "detector-python" ,
126+ & [ Language :: Python ] ,
127+ reg. clone( ) ,
128+ ) ) ,
129+ Box :: new( PatternDetector :: new(
130+ "detector-php" ,
131+ & [ Language :: Php ] ,
132+ reg. clone( ) ,
133+ ) ) ,
134+ Box :: new( PatternDetector :: new(
135+ "detector-swift" ,
136+ & [ Language :: Swift ] ,
137+ reg. clone( ) ,
138+ ) ) ,
139+ Box :: new( PatternDetector :: new(
140+ "detector-objc" ,
141+ & [ Language :: ObjC ] ,
142+ reg. clone( ) ,
143+ ) ) ,
144+ Box :: new( PatternDetector :: new(
145+ "detector-kotlin" ,
146+ & [ Language :: Kotlin ] ,
147+ reg. clone( ) ,
148+ ) ) ,
149+ Box :: new( PatternDetector :: new(
150+ "detector-erlang" ,
151+ & [ Language :: Erlang ] ,
152+ reg. clone( ) ,
153+ ) ) ,
154+ ] ;
155+ let scanner = Scanner :: new ( & reg, dets, Config :: default ( ) ) ;
156+
157+ // Scan the nested general fixtures root; test should not rely on per-file targets
158+ let root = workspace. join ( "fixtures/general" ) ;
159+ let findings = scanner. run ( std:: slice:: from_ref ( & root) ) . unwrap ( ) ;
160+
161+ // Assert we discovered at least one finding per intended language subdir
162+ let has_rust = findings
163+ . iter ( )
164+ . any ( |f| matches ! ( f. language, Language :: Rust ) ) ;
165+ let has_python = findings
166+ . iter ( )
167+ . any ( |f| matches ! ( f. language, Language :: Python ) ) ;
168+ let has_java = findings
169+ . iter ( )
170+ . any ( |f| matches ! ( f. language, Language :: Java ) ) ;
171+ let has_c = findings. iter ( ) . any ( |f| matches ! ( f. language, Language :: C ) ) ;
172+ let has_cpp = findings. iter ( ) . any ( |f| matches ! ( f. language, Language :: Cpp ) ) ;
173+ let has_go = findings. iter ( ) . any ( |f| matches ! ( f. language, Language :: Go ) ) ;
174+ let has_php = findings. iter ( ) . any ( |f| matches ! ( f. language, Language :: Php ) ) ;
175+ let has_swift = findings
176+ . iter ( )
177+ . any ( |f| matches ! ( f. language, Language :: Swift ) ) ;
178+ let has_objc = findings
179+ . iter ( )
180+ . any ( |f| matches ! ( f. language, Language :: ObjC ) ) ;
181+ let has_kotlin = findings
182+ . iter ( )
183+ . any ( |f| matches ! ( f. language, Language :: Kotlin ) ) ;
184+ let has_erlang = findings
185+ . iter ( )
186+ . any ( |f| matches ! ( f. language, Language :: Erlang ) ) ;
187+
188+ assert ! ( has_rust, "missing Rust findings in nested scan" ) ;
189+ assert ! ( has_python, "missing Python findings in nested scan" ) ;
190+ assert ! ( has_java, "missing Java findings in nested scan" ) ;
191+ assert ! ( has_c || has_cpp, "missing C/C++ findings in nested scan" ) ;
192+ assert ! ( has_go, "missing Go findings in nested scan" ) ;
193+ assert ! ( has_php, "missing PHP findings in nested scan" ) ;
194+ assert ! ( has_swift, "missing Swift findings in nested scan" ) ;
195+ assert ! ( has_objc, "missing ObjC findings in nested scan" ) ;
196+ assert ! ( has_kotlin, "missing Kotlin findings in nested scan" ) ;
197+ assert ! ( has_erlang, "missing Erlang findings in nested scan" ) ;
198+
199+ // Ensure deduplication does not erase per-language assets: group by (file, language)
200+ use std:: collections:: HashSet ;
201+ let mut file_lang_pairs = HashSet :: new ( ) ;
202+ for f in & findings {
203+ file_lang_pairs. insert ( ( f. file . display ( ) . to_string ( ) , f. language ) ) ;
204+ }
205+ // Expect multiple unique (file,language) pairs in nested scan
206+ assert ! (
207+ file_lang_pairs. len( ) >= 5 ,
208+ "unexpectedly low unique file/language pairs"
209+ ) ;
210+ }
0 commit comments