View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.buildcache.checksum;
20  
21  import java.nio.file.Path;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Properties;
25  
26  import org.apache.maven.artifact.handler.manager.ArtifactHandlerManager;
27  import org.apache.maven.buildcache.MultiModuleSupport;
28  import org.apache.maven.buildcache.NormalizedModelProvider;
29  import org.apache.maven.buildcache.ProjectInputCalculator;
30  import org.apache.maven.buildcache.RemoteCacheRepository;
31  import org.apache.maven.buildcache.xml.CacheConfig;
32  import org.apache.maven.execution.MavenSession;
33  import org.apache.maven.model.Dependency;
34  import org.apache.maven.project.MavenProject;
35  import org.eclipse.aether.RepositorySystem;
36  import org.eclipse.aether.RepositorySystemSession;
37  import org.eclipse.aether.artifact.DefaultArtifact;
38  import org.eclipse.aether.repository.RemoteRepository;
39  import org.eclipse.aether.resolution.ArtifactRequest;
40  import org.eclipse.aether.resolution.ArtifactResult;
41  import org.junit.jupiter.api.BeforeEach;
42  import org.junit.jupiter.api.Test;
43  import org.junit.jupiter.api.extension.ExtendWith;
44  import org.junit.jupiter.api.io.TempDir;
45  import org.mockito.ArgumentCaptor;
46  import org.mockito.Mock;
47  import org.mockito.junit.jupiter.MockitoExtension;
48  import org.mockito.junit.jupiter.MockitoSettings;
49  import org.mockito.quality.Strictness;
50  
51  import static org.junit.jupiter.api.Assertions.assertEquals;
52  import static org.junit.jupiter.api.Assertions.assertFalse;
53  import static org.junit.jupiter.api.Assertions.assertTrue;
54  import static org.mockito.ArgumentMatchers.any;
55  import static org.mockito.ArgumentMatchers.eq;
56  import static org.mockito.Mockito.verify;
57  import static org.mockito.Mockito.when;
58  
59  /**
60   * Test for snapshot artifact resolution bug fix in MavenProjectInput.
61   * This test verifies that when MavenProjectInput resolves snapshot dependencies,
62   * the remote repositories are properly set on the ArtifactRequest, enabling
63   * resolution from remote repositories.
64   */
65  @ExtendWith(MockitoExtension.class)
66  @MockitoSettings(strictness = Strictness.LENIENT)
67  class MavenProjectInputSnapshotResolutionTest {
68  
69      @Mock
70      private MavenProject project;
71  
72      @Mock
73      private MavenSession session;
74  
75      @Mock
76      private RepositorySystem repoSystem;
77  
78      @Mock
79      private RepositorySystemSession repositorySystemSession;
80  
81      @Mock
82      private NormalizedModelProvider normalizedModelProvider;
83  
84      @Mock
85      private MultiModuleSupport multiModuleSupport;
86  
87      @Mock
88      private ProjectInputCalculator projectInputCalculator;
89  
90      @Mock
91      private CacheConfig config;
92  
93      @Mock
94      private RemoteCacheRepository remoteCache;
95  
96      @Mock
97      private ArtifactHandlerManager artifactHandlerManager;
98  
99      @TempDir
100     Path tempDir;
101 
102     private MavenProjectInput mavenProjectInput;
103 
104     @BeforeEach
105     void setUp() {
106         // Setup basic mocks that MavenProjectInput constructor needs
107         when(session.getRepositorySession()).thenReturn(repositorySystemSession);
108         when(project.getBasedir()).thenReturn(tempDir.toFile());
109         when(project.getProperties()).thenReturn(new Properties());
110         when(config.getDefaultGlob()).thenReturn("*");
111         when(config.isProcessPlugins()).thenReturn("false");
112         when(config.getGlobalExcludePaths()).thenReturn(new java.util.ArrayList<>());
113         when(config.calculateProjectVersionChecksum()).thenReturn(Boolean.FALSE);
114 
115         // Mock the build object that MavenProjectInput constructor needs
116         org.apache.maven.model.Build build = new org.apache.maven.model.Build();
117         build.setDirectory(tempDir.toString());
118         build.setOutputDirectory(tempDir.resolve("target/classes").toString());
119         build.setTestOutputDirectory(tempDir.resolve("target/test-classes").toString());
120         build.setSourceDirectory(tempDir.resolve("src/main/java").toString());
121         build.setTestSourceDirectory(tempDir.resolve("src/test/java").toString());
122         build.setResources(new java.util.ArrayList<>());
123         build.setTestResources(new java.util.ArrayList<>());
124         when(project.getBuild()).thenReturn(build);
125 
126         // Mock additional project methods that might be needed
127         when(project.getDependencies()).thenReturn(new java.util.ArrayList<>());
128         when(project.getBuildPlugins()).thenReturn(new java.util.ArrayList<>());
129         when(project.getModules()).thenReturn(new java.util.ArrayList<>());
130         when(project.getPackaging()).thenReturn("jar");
131 
132         // Create the actual MavenProjectInput instance
133         mavenProjectInput = new MavenProjectInput(
134                 project,
135                 normalizedModelProvider,
136                 multiModuleSupport,
137                 projectInputCalculator,
138                 session,
139                 config,
140                 repoSystem,
141                 remoteCache,
142                 artifactHandlerManager);
143     }
144 
145     @Test
146     void testArtifactRequestWithRepositoriesSet() throws Exception {
147         // Given: A snapshot dependency and configured repositories
148         Dependency dependency = new Dependency();
149         dependency.setGroupId("com.example");
150         dependency.setArtifactId("test-artifact");
151         dependency.setVersion("1.0-SNAPSHOT");
152         dependency.setType("jar");
153 
154         // Mock repository setup
155         RemoteRepository centralRepo =
156                 new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2").build();
157         List<RemoteRepository> remoteRepositories = new ArrayList<>();
158         remoteRepositories.add(centralRepo);
159         when(project.getRemoteProjectRepositories()).thenReturn(remoteRepositories);
160 
161         // Create a mock ArtifactResult that will be returned by the repository system
162         DefaultArtifact resolvedArtifact =
163                 new DefaultArtifact("com.example", "test-artifact", "1.0-20231201.123456-1", "jar", null);
164 
165         ArtifactRequest originalRequest = new ArtifactRequest();
166         originalRequest.setArtifact(new DefaultArtifact("com.example", "test-artifact", "1.0-SNAPSHOT", "jar", null));
167         ArtifactResult artifactResult = new ArtifactResult(originalRequest);
168         artifactResult.setArtifact(resolvedArtifact);
169 
170         // Mock the repository system to return our result
171         when(repoSystem.resolveArtifact(eq(repositorySystemSession), any(ArtifactRequest.class)))
172                 .thenReturn(artifactResult);
173 
174         // When: Call the resolveArtifact method directly using reflection
175         java.lang.reflect.Method resolveArtifactMethod =
176                 MavenProjectInput.class.getDeclaredMethod("resolveArtifact", Dependency.class);
177         resolveArtifactMethod.setAccessible(true);
178 
179         try {
180             resolveArtifactMethod.invoke(mavenProjectInput, dependency);
181         } catch (Exception e) {
182             // We expect this to fail because ArtifactResult.isResolved() returns false
183             // But we can still verify that the ArtifactRequest was created correctly
184         }
185 
186         // Then: Verify that resolveArtifact was called with repositories set
187         ArgumentCaptor<ArtifactRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactRequest.class);
188         verify(repoSystem).resolveArtifact(eq(repositorySystemSession), requestCaptor.capture());
189 
190         ArtifactRequest capturedRequest = requestCaptor.getValue();
191 
192         // Verify that repositories are set on the request (this is the bug fix!)
193         assertFalse(
194                 capturedRequest.getRepositories().isEmpty(),
195                 "Repositories should be set on ArtifactRequest for snapshot resolution");
196 
197         // Verify the repository details
198         assertEquals(1, capturedRequest.getRepositories().size());
199         assertEquals("central", capturedRequest.getRepositories().get(0).getId());
200         assertEquals(
201                 "https://repo.maven.apache.org/maven2",
202                 capturedRequest.getRepositories().get(0).getUrl());
203 
204         // Verify the artifact being resolved
205         assertEquals("com.example", capturedRequest.getArtifact().getGroupId());
206         assertEquals("test-artifact", capturedRequest.getArtifact().getArtifactId());
207         assertEquals("1.0-SNAPSHOT", capturedRequest.getArtifact().getVersion());
208         assertEquals("jar", capturedRequest.getArtifact().getExtension());
209 
210         // Verify that getRemoteProjectRepositories was called
211         verify(project).getRemoteProjectRepositories();
212     }
213 
214     @Test
215     void testArtifactRequestWithoutRepositories() throws Exception {
216         // Given: A snapshot dependency with no remote repositories configured
217         Dependency dependency = new Dependency();
218         dependency.setGroupId("com.example");
219         dependency.setArtifactId("test-artifact");
220         dependency.setVersion("1.0-SNAPSHOT");
221         dependency.setType("jar");
222 
223         // Mock empty repository list
224         when(project.getRemoteProjectRepositories()).thenReturn(new ArrayList<>());
225 
226         // Create a mock ArtifactResult
227         DefaultArtifact resolvedArtifact =
228                 new DefaultArtifact("com.example", "test-artifact", "1.0-20231201.123456-1", "jar", null);
229 
230         ArtifactRequest originalRequest = new ArtifactRequest();
231         originalRequest.setArtifact(new DefaultArtifact("com.example", "test-artifact", "1.0-SNAPSHOT", "jar", null));
232         ArtifactResult artifactResult = new ArtifactResult(originalRequest);
233         artifactResult.setArtifact(resolvedArtifact);
234 
235         // Mock the repository system
236         when(repoSystem.resolveArtifact(eq(repositorySystemSession), any(ArtifactRequest.class)))
237                 .thenReturn(artifactResult);
238 
239         // When: Call the resolveArtifact method directly using reflection
240         java.lang.reflect.Method resolveArtifactMethod =
241                 MavenProjectInput.class.getDeclaredMethod("resolveArtifact", Dependency.class);
242         resolveArtifactMethod.setAccessible(true);
243 
244         try {
245             resolveArtifactMethod.invoke(mavenProjectInput, dependency);
246         } catch (Exception e) {
247             // We expect this to fail because ArtifactResult.isResolved() returns false
248             // But we can still verify that the ArtifactRequest was created correctly
249         }
250 
251         // Then: Verify that resolveArtifact was called
252         ArgumentCaptor<ArtifactRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactRequest.class);
253         verify(repoSystem).resolveArtifact(eq(repositorySystemSession), requestCaptor.capture());
254 
255         ArtifactRequest capturedRequest = requestCaptor.getValue();
256 
257         // Verify that repositories list is empty (as configured)
258         assertTrue(
259                 capturedRequest.getRepositories().isEmpty(),
260                 "Repositories list should be empty when no remote repositories are configured");
261 
262         // Verify the artifact being resolved
263         assertEquals("com.example", capturedRequest.getArtifact().getGroupId());
264         assertEquals("test-artifact", capturedRequest.getArtifact().getArtifactId());
265         assertEquals("1.0-SNAPSHOT", capturedRequest.getArtifact().getVersion());
266         assertEquals("jar", capturedRequest.getArtifact().getExtension());
267 
268         // Verify that getRemoteProjectRepositories was called
269         verify(project).getRemoteProjectRepositories();
270     }
271 
272     @Test
273     void testArtifactRequestWithMultipleRepositories() throws Exception {
274         // Given: A snapshot dependency with multiple repositories configured
275         Dependency dependency = new Dependency();
276         dependency.setGroupId("com.example");
277         dependency.setArtifactId("test-artifact");
278         dependency.setVersion("1.0-SNAPSHOT");
279         dependency.setType("jar");
280 
281         // Mock multiple repositories
282         List<RemoteRepository> remoteRepositories = new ArrayList<>();
283         remoteRepositories.add(
284                 new RemoteRepository.Builder("central", "default", "https://repo.maven.apache.org/maven2").build());
285         remoteRepositories.add(
286                 new RemoteRepository.Builder("spring-milestones", "default", "https://repo.spring.io/milestone")
287                         .build());
288 
289         when(project.getRemoteProjectRepositories()).thenReturn(remoteRepositories);
290 
291         // Create a mock ArtifactResult
292         DefaultArtifact resolvedArtifact =
293                 new DefaultArtifact("com.example", "test-artifact", "1.0-20231201.123456-1", "jar", null);
294 
295         ArtifactRequest originalRequest = new ArtifactRequest();
296         originalRequest.setArtifact(new DefaultArtifact("com.example", "test-artifact", "1.0-SNAPSHOT", "jar", null));
297         ArtifactResult artifactResult = new ArtifactResult(originalRequest);
298         artifactResult.setArtifact(resolvedArtifact);
299 
300         // Mock the repository system
301         when(repoSystem.resolveArtifact(eq(repositorySystemSession), any(ArtifactRequest.class)))
302                 .thenReturn(artifactResult);
303 
304         // When: Call the resolveArtifact method directly using reflection
305         java.lang.reflect.Method resolveArtifactMethod =
306                 MavenProjectInput.class.getDeclaredMethod("resolveArtifact", Dependency.class);
307         resolveArtifactMethod.setAccessible(true);
308 
309         try {
310             resolveArtifactMethod.invoke(mavenProjectInput, dependency);
311         } catch (Exception e) {
312             // We expect this to fail because ArtifactResult.isResolved() returns false
313             // But we can still verify that the ArtifactRequest was created correctly
314         }
315 
316         // Then: Verify that resolveArtifact was called with all repositories set
317         ArgumentCaptor<ArtifactRequest> requestCaptor = ArgumentCaptor.forClass(ArtifactRequest.class);
318         verify(repoSystem).resolveArtifact(eq(repositorySystemSession), requestCaptor.capture());
319 
320         ArtifactRequest capturedRequest = requestCaptor.getValue();
321 
322         // Verify that all repositories are set on the request
323         assertEquals(2, capturedRequest.getRepositories().size());
324         assertEquals("central", capturedRequest.getRepositories().get(0).getId());
325         assertEquals(
326                 "spring-milestones", capturedRequest.getRepositories().get(1).getId());
327 
328         // Verify that getRemoteProjectRepositories was called
329         verify(project).getRemoteProjectRepositories();
330     }
331 }