1+ /*
2+ * The MIT License
3+ *
4+ * Copyright 2025 Jenkins Contributors
5+ *
6+ * Permission is hereby granted, free of charge, to any person obtaining a copy
7+ * of this software and associated documentation files (the "Software"), to deal
8+ * in the Software without restriction, including without limitation the rights
9+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+ * copies of the Software, and to permit persons to whom the Software is
11+ * furnished to do so, subject to the following conditions:
12+ *
13+ * The above copyright notice and this permission notice shall be included in
14+ * all copies or substantial portions of the Software.
15+ *
16+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+ * THE SOFTWARE.
23+ */
24+
25+ package jenkins .branch ;
26+
27+ import jenkins .scm .impl .SingleSCMNavigator ;
28+ import jenkins .scm .impl .SingleSCMSource ;
29+ import jenkins .scm .impl .mock .MockSCM ;
30+ import jenkins .scm .impl .mock .MockSCMController ;
31+ import jenkins .scm .impl .mock .MockSCMHead ;
32+ import org .junit .Rule ;
33+ import org .junit .Test ;
34+ import org .jvnet .hudson .test .JenkinsRule ;
35+ import java .util .Collections ;
36+ import static org .junit .Assert .*;
37+
38+ public class OrganizationFolderMultiProjectTest {
39+
40+ @ Rule
41+ public JenkinsRule r = new JenkinsRule ();
42+
43+ /**
44+ * Tests that new configuration fields have correct default values.
45+ * Verifies createMultipleProjects and projectNamePattern defaults.
46+ */
47+ @ Test
48+ public void testMultiProjectConfigurationDefaults () throws Exception {
49+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "top" );
50+
51+ // Test default values
52+ assertFalse ("createMultipleProjects should default to false" , folder .isCreateMultipleProjects ());
53+ assertEquals ("Default project name pattern should be correct" , ".*?(-[^.]+).*" , folder .getProjectNamePattern ());
54+
55+ // Test configuration round-trip
56+ folder = r .configRoundtrip (folder );
57+
58+ assertFalse ("createMultipleProjects should still be false after round-trip" , folder .isCreateMultipleProjects ());
59+ assertEquals ("Project name pattern should be preserved after round-trip" , ".*?(-[^.]+).*" , folder .getProjectNamePattern ());
60+ }
61+
62+ /**
63+ * Tests setter methods for new configuration fields.
64+ */
65+ @ Test
66+ public void testMultiProjectConfigurationSetters () throws Exception {
67+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "top" );
68+
69+ // Test setting values
70+ folder .setCreateMultipleProjects (true );
71+ folder .setProjectNamePattern (".*?-(dev|prod).*" );
72+
73+ assertTrue ("createMultipleProjects should be true after setting" , folder .isCreateMultipleProjects ());
74+ assertEquals ("Project name pattern should be updated" , ".*?-(dev|prod).*" , folder .getProjectNamePattern ());
75+
76+ // Test null pattern handling
77+ folder .setProjectNamePattern (null );
78+ assertEquals ("Null pattern should default to fallback" , ".*?(-[^.]+).*" , folder .getProjectNamePattern ());
79+ }
80+
81+ /**
82+ * Tests configuration persistence through round-trip.
83+ */
84+ @ Test
85+ public void testMultiProjectFormSubmission () throws Exception {
86+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "top" );
87+
88+ // Test through configuration round-trip with modified values
89+ folder .setCreateMultipleProjects (true );
90+ folder .setProjectNamePattern (".*?-(test|staging).*" );
91+
92+ OrganizationFolder configured = r .configRoundtrip (folder );
93+
94+ assertTrue ("createMultipleProjects should be true after configuration" , configured .isCreateMultipleProjects ());
95+ assertEquals ("Project name pattern should be saved correctly" , ".*?-(test|staging).*" , configured .getProjectNamePattern ());
96+ }
97+
98+ /**
99+ * Tests project name extraction functionality when multiple projects are enabled.
100+ */
101+ @ Test
102+ public void testProjectNameExtractionWithMultipleProjects () throws Exception {
103+ try (MockSCMController controller = MockSCMController .create ()) {
104+ // Create repository
105+ controller .createRepository ("myproject" );
106+
107+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "test-org" );
108+ folder .setCreateMultipleProjects (true );
109+ folder .setProjectNamePattern (".*?(-[^.]+).*" );
110+
111+ // Add mock navigator and source
112+ SingleSCMSource source = new SingleSCMSource ("myproject-source" ,
113+ new MockSCM (controller , "myproject" , new MockSCMHead ("master" ), null ));
114+ folder .getNavigators ().add (new SingleSCMNavigator ("myproject" , Collections .singletonList (source )));
115+
116+ // Trigger scan
117+ folder .scheduleBuild (0 );
118+ r .waitUntilNoActivity ();
119+
120+ // Verify that organization folder processes the scan
121+ assertNotNull ("Organization folder should process scan" , folder .getComputation ());
122+
123+ // Test configuration values
124+ assertTrue ("createMultipleProjects should be true" , folder .isCreateMultipleProjects ());
125+ assertEquals ("Project name pattern should be set" , ".*?(-[^.]+).*" , folder .getProjectNamePattern ());
126+ }
127+ }
128+
129+ /**
130+ * Tests that configuration changes trigger rescans appropriately.
131+ */
132+ @ Test
133+ public void testConfigurationChangeTriggersRescan () throws Exception {
134+ try (MockSCMController controller = MockSCMController .create ()) {
135+ controller .createRepository ("test-repo" );
136+
137+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "test-org" );
138+
139+ // Add source for scanning
140+ SingleSCMSource source = new SingleSCMSource ("test-repo-source" ,
141+ new MockSCM (controller , "test-repo" , new MockSCMHead ("master" ), null ));
142+ folder .getNavigators ().add (new SingleSCMNavigator ("test-repo" , Collections .singletonList (source )));
143+
144+ // Perform initial scan
145+ folder .scheduleBuild (0 );
146+ r .waitUntilNoActivity ();
147+
148+ // Change createMultipleProjects setting
149+ folder .setCreateMultipleProjects (true );
150+ folder .save ();
151+
152+ // Change projectNamePattern setting
153+ folder .setProjectNamePattern (".*?(-[^.]+).*" );
154+ folder .save ();
155+
156+ // Verify that folder is properly configured
157+ assertTrue ("createMultipleProjects should be true" , folder .isCreateMultipleProjects ());
158+ assertEquals ("Project name pattern should be set" , ".*?(-[^.]+).*" , folder .getProjectNamePattern ());
159+
160+ // Note: In a real test environment, we would verify that rescans are triggered
161+ // but this requires more complex setup with listeners
162+ }
163+ }
164+
165+ /**
166+ * Tests backward compatibility - that existing configurations still work.
167+ */
168+ @ Test
169+ public void testBackwardCompatibility () throws Exception {
170+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "legacy-folder" );
171+
172+ // Ensure default values work for existing configurations
173+ assertFalse ("Legacy folders should have createMultipleProjects as false" , folder .isCreateMultipleProjects ());
174+ assertNotNull ("Legacy folders should have default project name pattern" , folder .getProjectNamePattern ());
175+
176+ // Test that existing functionality still works
177+ try (MockSCMController controller = MockSCMController .create ()) {
178+ controller .createRepository ("legacy-project" );
179+
180+ // Add source for scanning
181+ SingleSCMSource source = new SingleSCMSource ("legacy-project-source" ,
182+ new MockSCM (controller , "legacy-project" , new MockSCMHead ("master" ), null ));
183+ folder .getNavigators ().add (new SingleSCMNavigator ("legacy-project" , Collections .singletonList (source )));
184+
185+ folder .scheduleBuild (0 );
186+ r .waitUntilNoActivity ();
187+
188+ // Folder should work normally even without new features enabled
189+ assertNotNull ("Legacy folder should still function" , folder .getComputation ());
190+ }
191+ }
192+
193+ /**
194+ * Tests edge cases for the project name pattern.
195+ */
196+ @ Test
197+ public void testProjectNamePatternEdgeCases () throws Exception {
198+ OrganizationFolder folder = r .jenkins .createProject (OrganizationFolder .class , "top" );
199+
200+ // Test empty pattern
201+ folder .setProjectNamePattern ("" );
202+ assertEquals ("Empty pattern should be handled" , "" , folder .getProjectNamePattern ());
203+
204+ // Test pattern with special regex characters
205+ folder .setProjectNamePattern (".*?\\ -(test|prod)\\ -[0-9]+.*" );
206+ assertEquals ("Pattern with special characters should be preserved" ,
207+ ".*?\\ -(test|prod)\\ -[0-9]+.*" , folder .getProjectNamePattern ());
208+
209+ // Test null pattern (should use default fallback)
210+ folder .setProjectNamePattern (null );
211+ assertEquals ("Null pattern should use fallback" , ".*?(-[^.]+).*" , folder .getProjectNamePattern ());
212+ }
213+ }
0 commit comments